开发者

How to decorate (monkeypatch...) a Python class with methods from another class?

开发者 https://www.devze.com 2023-02-13 09:12 出处:网络
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 leve

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 the get_filename method present in email.Message, that allows you to easily retrieve the filename from a Content-disposition: attachment; filename="fghi.xyz" header;

  • httplib.HTTPMessage has getparam, getplist and parseplist methods but AFAIK, they are not and cannot be used outside of the content-type header parsing;

  • email.Message has a generic get_param method to parse any RFC822 header with parameters, such as content-disposition or content-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


  1. In Python 2.6. I can't use 2.7 nor 3.x in production.

  2. httplib.HTTPMessage inherits from mimetools.Message and rfc822.Message while email.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 ! :-)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号