Python: Как украсить специальный (дундер) метод - PullRequest
2 голосов
/ 06 апреля 2019

Обертывание специальным методом работает, но не оказывает желаемого влияния на поведение экземпляра.

Например, украшение метода a.__call__ (экземпляра a) действительно вступит в силу, если я вызову a.__call__(x), но не если я вызову a(x).

. Рассмотрим следующеефункция, которая делает декоратор, который предварительно обрабатывает ввод:

def input_wrap_decorator(preprocess):
    def decorator(func):
        def func_wrapper(*args, **kwargs):
            return func(preprocess(*args, **kwargs))
        return func_wrapper
    return decorator

Рассмотрим этот простой класс:

class A:
    def __call__(self, k):
        return "{}({})".format(self.__class__.__name__, k)

Демонстрация его удивительной функциональности:

>>> a = A()
>>> a(7)
'A(7)'

Теперь произнеситеЯ хочу сделать что-то критическое: умножить все входные данные на __call__ на 10, используя input_wrap_decorator.Вот что происходит:

>>> a = A()
>>> a.__call__ = input_wrap_decorator(preprocess=lambda x: x * 10)(a.__call__)
>>> a.__call__(7)  # __call__ works as expected
'A(70)'
>>> a(7)  # but a(.) does not!
'A(7)'

Происходит что-то неясное, что знает только взрослый питон ...

1 Ответ

3 голосов
/ 06 апреля 2019

Как указано в Специальный метод поиска ,

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

Итак, вы можете сделать это так:

def input_wrap_decorator(preprocess):
    def decorator(func):
        def func_wrapper(self, *args, **kwargs):
            return func(self, preprocess(*args, **kwargs))
        return func_wrapper
    return decorator

class A:
    def __call__(self, k):
        return "{}({})".format(self.__class__.__name__, k)

a = A()

# A.__call__ will be used by a(7), not a.__call__
A.__call__ = input_wrap_decorator(preprocess=lambda x: x * 10)(A.__call__)

print(a.__call__(7))
# A(70)
print(a(7))
# A(70)

Обратите внимание, что я выделил self в func_wrapper, чтобы он не передавался в preprocess с другими аргументами.

И, конечно, вы можете использовать синтаксический сахар для декораторов:

class A:
    @input_wrap_decorator(preprocess=lambda x: x * 10)
    def __call__(self, k):
        return "{}({})".format(self.__class__.__name__, k)

a = A()    
print(a.__call__(7))
# A(70)
print(a(7))
# A(70)
...