如何使用另一个类的方法装饰(monkeypatch...)Python 类?

发布于 2024-10-19 10:54:21 字数 3327 浏览 4 评论 0原文

httplib.HTTPMessageemail.message.Message 类[1] 都实现了 RFC822 标头解析的方法。不幸的是,它们有不同的实现[2],并且不提供相同级别的功能。

困扰我的一个例子是:

  • httplib.HTTPMessage 缺少 email.Message 中存在的 get_filename 方法,该方法允许您轻松地从 Content-disposition: 附件中检索文件名; filename="fghi.xyz" header;

  • httplib.HTTPMessagegetparamgetplistparseplist 方法,但据我所知,它们不是也不可能是在 content-type 标头解析之外使用;

  • email.Message 有一个通用的 get_param 方法来解析带有参数的任何 RFC822 标头,例如 content-dispositioncontent -type

因此,我想要 httplib.HTTPMessageemail.message.Messageget_filenameget_param 方法,但是,当然,我无法修补 httplib.HTTPMessage 因为它在标准库中... :-q

最后,这里是装饰器主题... :-)

我成功创建了一个 Monkeypatch_http_message 函数用我缺少的解析方法来装饰 httplib.HTTPMessage

def monkeypatch_http_message(obj):
    from email import utils
    from email.message import (
        _parseparam,
        _unquotevalue,
    )
    cls = obj.__class__

    # methods **copied** from email.message.Message source code
    def _get_params_preserve(self, failobj, header): ...
    def get_params(self, failobj=None, header='content-type', 
                   unquote=True): ...
    def get_param(self, param, failobj=None, header='content-type', 
                  unquote=True): ...
    def get_filename(self, failobj=None): ...

    # monkeypatching httplib.Message
    cls._get_params_preserve = _get_params_preserve
    cls.get_params = get_params
    cls.get_param = get_param
    cls.get_filename = get_filename

现在我可以做:

import mechanize
from some.module import monkeypatch_http_message
browser = mechanize.Browser()

# in that form, browser.retrieve returns a temporary filename 
# and an httplib.HTTPMessage instance
(tmp_filename, headers) = browser.retrieve(someurl) 

# monkeypatch the httplib.HTTPMessage instance
monkeypatch_http_message(headers)

# yeah... my original filename, finally
filename = headers.get_filename()

这里的问题是我从字面上复制了源类中的装饰方法代码,我'我想避免。

所以,我尝试通过引用源方法来装饰:

def monkeypatch_http_message(obj):
    from email import utils
    from email.message import (
        _parseparam,
        _unquotevalue,
        Message    # XXX added
    )
    cls = obj.__class__

    # monkeypatching httplib.Message
    cls._get_params_preserve = Message._get_params_preserve
    cls.get_params = Message.get_params
    cls.get_param = Message.get_param
    cls.get_filename = Message.get_filename

但这给了我:

Traceback (most recent call last):
  File "client.py", line 224, in <module>
    filename = headers.get_filename()
TypeError: unbound method get_filename() must be called with Message instance as first argument (got nothing instead)

我现在正在抓耳挠腮......如何在不直接复制源方法的情况下装饰我的类?

有什么建议吗? :-)

问候,

乔治·马丁


  1. 在 Python 2.6 中。我无法在生产中使用 2.7 或 3.x。

  2. httplib.HTTPMessage继承自mimetools.Messagerfc822.Message,而email.Message有自己的

Both httplib.HTTPMessage and email.message.Message classes[1] implements methods for RFC822 headers parsing. Unfortunately, they have different implementations[2] and they do not provide the same level of functionality.

One example that is bugging me is that:

  • httplib.HTTPMessage is missing the get_filename method present in email.Message, that allows you to easily retrieve the filename from a Content-disposition: attachment; filename="fghi.xyz" header;

  • httplib.HTTPMessage has getparam, getplist and parseplist methods but AFAIK, they are not and cannot be used outside of the content-type header parsing;

  • email.Message has a generic get_param method to parse any RFC822 header with parameters, such as content-disposition or content-type.

Thus, I want the get_filename or get_param methods of email.message.Message in httplib.HTTPMessage but, of course, I can't patch httplib.HTTPMessage as it's in the standard library... :-q

And finally, here comes the decorator subject... :-)

I succesfully created a monkeypatch_http_message function to decorate an httplib.HTTPMessage with my missing parsing methods:

def monkeypatch_http_message(obj):
    from email import utils
    from email.message import (
        _parseparam,
        _unquotevalue,
    )
    cls = obj.__class__

    # methods **copied** from email.message.Message source code
    def _get_params_preserve(self, failobj, header): ...
    def get_params(self, failobj=None, header='content-type', 
                   unquote=True): ...
    def get_param(self, param, failobj=None, header='content-type', 
                  unquote=True): ...
    def get_filename(self, failobj=None): ...

    # monkeypatching httplib.Message
    cls._get_params_preserve = _get_params_preserve
    cls.get_params = get_params
    cls.get_param = get_param
    cls.get_filename = get_filename

Now I can do:

import mechanize
from some.module import monkeypatch_http_message
browser = mechanize.Browser()

# in that form, browser.retrieve returns a temporary filename 
# and an httplib.HTTPMessage instance
(tmp_filename, headers) = browser.retrieve(someurl) 

# monkeypatch the httplib.HTTPMessage instance
monkeypatch_http_message(headers)

# yeah... my original filename, finally
filename = headers.get_filename()

The issue here is that I literally copied the decorating methods code from the source class, which I'd like to avoid.

So, I tried decorating by referencing the source methods:

def monkeypatch_http_message(obj):
    from email import utils
    from email.message import (
        _parseparam,
        _unquotevalue,
        Message    # XXX added
    )
    cls = obj.__class__

    # monkeypatching httplib.Message
    cls._get_params_preserve = Message._get_params_preserve
    cls.get_params = Message.get_params
    cls.get_param = Message.get_param
    cls.get_filename = Message.get_filename

But that gives me:

Traceback (most recent call last):
  File "client.py", line 224, in <module>
    filename = headers.get_filename()
TypeError: unbound method get_filename() must be called with Message instance as first argument (got nothing instead)

I'm scratching my head now... how can I decorate my class without literally copying the source methods ?

Any suggestions ? :-)

Regards,

Georges Martin


  1. In Python 2.6. I can't use 2.7 nor 3.x in production.

  2. httplib.HTTPMessage inherits from mimetools.Message and rfc822.Message while email.Message has its own implementation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡淡绿茶香 2024-10-26 10:54:21

在 Python 3.x 中,未绑定方法消失了,因此在这种情况下您只需获取文件对象,并且您的第二个示例将起作用:

>>> class C():
...   def demo(): pass
... 
>>> C.demo
<function demo at 0x1fed6d8>

在 Python 2.x 中,您可以通过未绑定方法访问底层函数,也可以通过检索它来访问底层函数直接从类字典中获取(从而绕过将其转换为未绑定方法的正常查找过程):

>>> class C():
...   def demo(): pass
... 
>>> C.demo.im_func                  # Retrieve it from the unbound method
<function demo at 0x7f463486d5f0>
>>> C.__dict__["demo"]              # Retrieve it directly from the class dict
<function demo at 0x7f463486d5f0>

后一种方法的优点是与 Python 3.x 向前兼容。

In Python 3.x, unbound methods go away so you'll just get the file objects in this case and your second example will work:

>>> class C():
...   def demo(): pass
... 
>>> C.demo
<function demo at 0x1fed6d8>

In Python 2.x, you can either access the underlying function via the unbound method or by retrieving it directly from the class dictionary (thus bypassing the normal lookup process that turns it into an unbound method):

>>> class C():
...   def demo(): pass
... 
>>> C.demo.im_func                  # Retrieve it from the unbound method
<function demo at 0x7f463486d5f0>
>>> C.__dict__["demo"]              # Retrieve it directly from the class dict
<function demo at 0x7f463486d5f0>

The latter approach has the benefit of being forward compatible with Python 3.x.

冷︶言冷语的世界 2024-10-26 10:54:21

@ncoghlan:我无法在注释中放入缩进代码,所以这里再次:

def monkeypatch_http_message(obj):
    import httplib
    assert isinstance(obj, httplib.HTTPMessage)
    cls = obj.__class__

    from email import utils
    from email.message import (_parseparam, _unquotevalue, Message)
    funcnames = ('_get_params_preserve', 'get_params', 'get_param', 'get_filename')
    for funcname in funcnames:
        cls.__dict__[funcname] = Message.__dict__[funcname]

谢谢! :-)

@ncoghlan: I can't put indented code in comments, so here it is again:

def monkeypatch_http_message(obj):
    import httplib
    assert isinstance(obj, httplib.HTTPMessage)
    cls = obj.__class__

    from email import utils
    from email.message import (_parseparam, _unquotevalue, Message)
    funcnames = ('_get_params_preserve', 'get_params', 'get_param', 'get_filename')
    for funcname in funcnames:
        cls.__dict__[funcname] = Message.__dict__[funcname]

Thanks ! :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文