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

时间:2011-02-24 15:00:45

标签: python class decorator monkeypatching rfc822

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

困扰我的一个例子是:

  • httplib.HTTPMessage缺少get_filename中的email.Message方法,可让您轻松从Content-disposition: attachment; filename="fghi.xyz"标题中检索文件名;

    < / LI>
  • httplib.HTTPMessagegetparamgetplistparseplist方法,但有AFAIK,它们不能也不能在content-type标头解析之外使用

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

因此,我希望get_filename中的get_paramemail.message.Message httplib.HTTPMessage方法,但当然,我无法修补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有自己的实施。

2 个答案:

答案 0 :(得分:2)

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

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

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

>>> 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向前兼容的优点。

答案 1 :(得分:1)

@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]

谢谢! : - )