Как украсить (monkeypatch ...) класс Python методами из другого класса? - PullRequest
1 голос
/ 24 февраля 2011

Классы httplib.HTTPMessage и email.message.Message [1] реализуют методы синтаксического анализа заголовков RFC822.К сожалению, они имеют разные реализации [2] и не обеспечивают одинаковый уровень функциональности.

Один из примеров, который меня беспокоит, это то, что:

  • httplib.HTTPMessageотсутствует метод get_filename, присутствующий в email.Message, который позволяет легко извлечь имя файла из заголовка Content-disposition: attachment; filename="fghi.xyz";

  • httplib.HTTPMessage имеет getparam, getplist и parseplist методы, но AFAIK, они не используются и не могут использоваться вне синтаксического анализа заголовка content-type;

  • email.Message имеет универсальный метод get_param для анализа любогоЗаголовок RFC822 с параметрами, такими как content-disposition или content-type.

Таким образом, я хочу, чтобы get_filename или get_param методы email.message.Message в httplib.HTTPMessage, но,конечно, я не могу исправить httplib.HTTPMessage, как в стандартной библиотеке ...: -q

И, наконец, вот тема декоратора ...: -)

Iуспешно создал 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.Message и rfc822.Message, в то время как email.Message имеет собственную реализацию.

Ответы [ 2 ]

2 голосов
/ 25 февраля 2011

В 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.

1 голос
/ 25 февраля 2011

@ 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]

Спасибо! : -)

...