Подтвердить что ты не робот

Как я могу перехватить вызовы на "волшебные" методы python в новых классах стилей?

Я пытаюсь перехватить вызовы в методах двойного подчеркивания python в новых классах стилей. Это тривиальный пример, но он показывает намерение:

class ShowMeList(object):
    def __init__(self, it):
        self._data = list(it)

    def __getattr__(self, name):
        attr = object.__getattribute__(self._data, name)
        if callable(attr):
            def wrapper(*a, **kw):
                print "before the call"
                result = attr(*a, **kw)
                print "after the call"
                return result
            return wrapper
        return attr

Если я использую этот прокси-объект вокруг списка, я получаю ожидаемое поведение для методов, отличных от магии, но моя функция-обертка никогда не вызывается для магических методов.

>>> l = ShowMeList(range(8))

>>> l #call to __repr__
<__main__.ShowMeList object at 0x9640eac>

>>> l.append(9)
before the call
after the call

>> len(l._data)
9

Если я не наследую объект (первая строка class ShowMeList:), все работает так, как ожидалось:

>>> l = ShowMeList(range(8))

>>> l #call to __repr__
before the call
after the call
[0, 1, 2, 3, 4, 5, 6, 7]

>>> l.append(9)
before the call
after the call

>> len(l._data)
9

Как выполнить этот перехват с новыми классами стилей?

4b9b3361

Ответ 1

По соображениям производительности Python всегда ищет класс (и родительские классы) __dict__ для магических методов и не использует механизм поиска нормального атрибута. Обходным путем является использование метакласса для автоматического добавления прокси для магических методов во время создания класса; Я использовал эту технику, чтобы избежать необходимости писать методы call-through для шаблонов для классов-оболочек, например.

class Wrapper(object):
    """Wrapper class that provides proxy access to an instance of some
       internal instance."""

    __wraps__  = None
    __ignore__ = "class mro new init setattr getattr getattribute"

    def __init__(self, obj):
        if self.__wraps__ is None:
            raise TypeError("base class Wrapper may not be instantiated")
        elif isinstance(obj, self.__wraps__):
            self._obj = obj
        else:
            raise ValueError("wrapped object must be of %s" % self.__wraps__)

    # provide proxy access to regular attributes of wrapped object
    def __getattr__(self, name):
        return getattr(self._obj, name)

    # create proxies for wrapped object double-underscore attributes
    class __metaclass__(type):
        def __init__(cls, name, bases, dct):

            def make_proxy(name):
                def proxy(self, *args):
                    return getattr(self._obj, name)
                return proxy

            type.__init__(cls, name, bases, dct)
            if cls.__wraps__:
                ignore = set("__%s__" % n for n in cls.__ignore__.split())
                for name in dir(cls.__wraps__):
                    if name.startswith("__"):
                        if name not in ignore and name not in dct:
                            setattr(cls, name, property(make_proxy(name)))

Использование:

class DictWrapper(Wrapper):
    __wraps__ = dict

wrapped_dict = DictWrapper(dict(a=1, b=2, c=3))

# make sure it worked....
assert "b" in wrapped_dict                        # __contains__
assert wrapped_dict == dict(a=1, b=2, c=3)        # __eq__
assert "'a': 1" in str(wrapped_dict)              # __str__
assert wrapped_dict.__doc__.startswith("dict()")  # __doc__

Ответ 2

Использование __getattr__ и __getattribute__ - это последние ресурсы класса для ответа на получение атрибута.

Рассмотрим следующее:

>>> class C:
    x = 1
    def __init__(self):
        self.y = 2
    def __getattr__(self, attr):
        print(attr)

>>> c = C()
>>> c.x
1
>>> c.y
2
>>> c.z
z

Метод __getattr__ вызывается только тогда, когда ничего не работает (он не будет работать на операторах, и вы можете прочитать об этом здесь).

В вашем примере __repr__ и многие другие магические методы уже определены в классе object.

Можно сделать одно, подумать, и определить эти магические методы и затем вызвать метод __getattr__. Проверьте этот другой вопрос мной и его ответами (ссылка), чтобы увидеть, как это делает код.

Ответ 4

Вырезать и скопировать из документации:

Для классов старого стиля специальные методы всегда просматриваются точно так же, как и любой другой метод или атрибут.

Для классов нового стиля неявные вызовы специальных методов гарантируются только корректно, если они определены в типе объектов, а не в словаре экземпляров объектов.