如何用另一类的方法装饰(猴子补丁)一个Python类?
这段内容主要讲的是两个类,httplib.HTTPMessage
和 email.message.Message
,它们都能处理RFC822格式的头信息,但实现方式不同,功能也不一样。
让我来举几个例子,看看它们之间的区别:
httplib.HTTPMessage
没有get_filename
这个方法,而email.Message
有。这个方法可以让你很方便地从像Content-disposition: attachment; filename="fghi.xyz"
这样的头信息中获取文件名。httplib.HTTPMessage有
getparam
、getplist
和parseplist
这些方法,但据我所知,它们只能在解析content-type
头信息时使用,不能在其他地方用。email.Message
有一个通用的get_param
方法,可以解析任何带参数的RFC822头信息,比如content-disposition
或content-type
。
所以,我希望能把email.message.Message
中的get_filename
或get_param
方法加到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)
我现在有点困惑... 我该如何在不直接复制源方法的情况下装饰我的类呢?
有什么建议吗?:-)
祝好,
Georges Martin
我在使用Python 2.6,不能在生产环境中使用2.7或3.x。
httplib.HTTPMessage
是从mimetools.Message
和rfc822.Message
继承而来的,而email.Message
有自己的实现。
2 个回答
@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]
谢谢!:-)
在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中也能继续使用。