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

如何用另一个类的方法装饰(monkeypatch…)一个Python类?,python,class,decorator,monkeypatching,rfc822,Python,Class,Decorator,Monkeypatching,Rfc822,httplib.HTTPMessage和email.message.messageclasses[1]都实现了RFC822头解析的方法。不幸的是,它们有不同的实现[2],并且它们不提供相同级别的功能 困扰我的一个例子是: httplib.HTTPMessage缺少email.Message中的get_filename方法,该方法允许您从内容配置:附件中轻松检索文件名;filename=“fghi.xyz”标题 httplib.HTTPMessage具有getparam、getplist和par

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

困扰我的一个例子是:

  • httplib.HTTPMessage
    缺少
    email.Message
    中的
    get_filename
    方法,该方法允许您从
    内容配置:附件中轻松检索文件名;filename=“fghi.xyz”
    标题

  • httplib.HTTPMessage
    具有
    getparam
    getplist
    parseplist
    方法,但在
    内容类型
    头解析之外不能使用它们

  • email.Message
    有一个通用的
    get_param
    方法来解析任何带有参数的RFC822头,如
    内容配置
    内容类型

因此,我想要
httplib.HTTPMessage
中的
get_filename
get_param
方法
email.message.message
,但是,当然,我不能修补
httplib.HTTPMessage
,因为它在标准库中-q

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

我成功地创建了一个
monkeypatch\u http\u 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)
回溯(最近一次呼叫最后一次):
文件“client.py”,第224行,在
filename=headers.get_filename()
TypeError:必须使用Message instance作为第一个参数调用未绑定的方法get_filename()(而不是获取任何内容)
我现在在挠头。。。如何在不复制源方法的情况下装饰类

有什么建议吗?:-)

问候,

乔治·马丁


  • 在Python2.6中。我不能在生产中使用2.7或3.x

  • httplib.HTTPMessage
    继承自
    mimetools.Message
    rfc822.Message
    ,而
    email.Message
    有自己的实现


  • 在Python3.x中,未绑定的方法消失了,因此在本例中,您只需要获取文件对象,第二个示例就可以了:

    >>> class C():
    ...   def demo(): pass
    ... 
    >>> C.demo
    <function demo at 0x1fed6d8>
    
    >>C类()
    ...   def demo():通过
    ... 
    >>>C.演示
    
    在Python2.x中,您可以通过unbound方法访问底层函数,也可以直接从类字典中检索底层函数(从而绕过将其转换为unbound方法的正常查找过程):

    >>C类()
    ...   def demo():通过
    ... 
    >>>C.demo.im#func#从unbound方法中检索它
    >>>C.uu dict_uu[“demo”]#直接从类dict中检索它
    

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

    @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]
    
    谢谢!:-)

    Waaaaaay更干净:-)def monkeypatch_http_message(obj):import-httplib assert-isinstance(obj,httplib.HTTPMessage)cls=obj.\uu class_uuuuuuuuuuuu来自电子邮件导入utils来自email.message导入(\u parseparam,\u unquotevalue,message)funcnames=在funcnames:cls中为funcname指定(“\u get\u params\u preserve”、“get\u params”、“get\u param”、“get\u filename”)。\u dict\uuuuuuuu[funcname]=Message.\uu dict\uuuuuuuuuu[funcname]谢谢:-)我尝试此操作时(在@classmethod上,顺便说一句)收到错误:“TypeError:'dictproxy'对象不支持项分配”。此技术不适用于从“对象”(新样式类)派生的对象。
    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]