【问题标题】:How to decorate (monkeypatch...) a Python class with methods from another class?如何用另一个类的方法装饰(monkeypatch ...)一个Python类?
【发布时间】:2011-07-03 15:27:03
【问题描述】:

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

困扰我的一个例子是:

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

  • httplib.HTTPMessage 具有 getparamgetplistparseplist 方法,但 AFAIK,它们不能也不能在 content-type 标头解析之外使用;

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

因此,我想要httplib.HTTPMessage 中的email.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.Messageemail.Message 有自己的实现。

【问题讨论】:

    标签: python class decorator monkeypatching rfc822


    【解决方案1】:

    @ncoghlan:我不能在 cmets 中放缩进代码,所以又来了:

    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]
    

    谢谢! :-)

    【讨论】:

    • 我得到错误:“TypeError:'dictproxy'对象不支持项目分配”当我尝试这个(在@classmethod,顺便说一句)。此技术不适用于从“对象”(新型类)派生的对象。
    【解决方案2】:

    在 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 前向兼容。

    【讨论】:

    • Waaaaaay 干净多了 :-) 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') 用于 funcnames 中的 funcname:cls.__dict__[funcname] = Message.__dict__[funcname] 谢谢! :-)
    猜你喜欢
    • 2021-12-26
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-31
    • 2019-11-02
    • 2021-10-24
    相关资源
    最近更新 更多