How to decorate (monkeypatch...) a Python class with methods from another class?
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 b开发者_高级运维ugging me is that:
httplib.HTTPMessage
is missing theget_filename
method present inemail.Message
, that allows you to easily retrieve the filename from aContent-disposition: attachment; filename="fghi.xyz"
header;httplib.HTTPMessage
hasgetparam
,getplist
andparseplist
methods but AFAIK, they are not and cannot be used outside of thecontent-type
header parsing;email.Message
has a genericget_param
method to parse any RFC822 header with parameters, such ascontent-disposition
orcontent-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
In Python 2.6. I can't use 2.7 nor 3.x in production.
httplib.HTTPMessage
inherits frommimetools.Message
andrfc822.Message
whileemail.Message
has its own implementation.
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.
@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 ! :-)
精彩评论