Декораторы классов для методов в классах

Как работают декораторы классов для методов в классах? Вот пример того, что я сделал с помощью некоторых экспериментов:

      from functools import wraps


class PrintLog(object):

    def __call__(self, func):
        @wraps(func)
        def wrapped(*args):
            print('I am a log')
            return func(*args)
        return wrapped


class foo(object):
    def __init__(self, rs: str) -> None:
        self.ter = rs

    @PrintLog()
    def baz(self) -> None:
        print('inside baz')


bar = foo('2')
print('running bar.baz()')
bar.baz()

И это отлично работает. Однако у меня сложилось впечатление, что декораторов не нужно вызывать с (), но когда я убираю скобки из @PrintLog(), я получаю эту ошибку:

          def baz(self) -> None:
TypeError: PrintLog() takes no arguments

Я что-то упускаю/не понимаю? Я также пытался передать одноразовый аргумент с помощью __init__(), и это работает.

      class PrintLog(object):

    def __init__(self, useless):
        print(useless)

    def __call__(self, func):
        @wraps(func)
        def wrapped(*args):
            print('I am a log')
            return func(*args)
        return wrapped

class foo(object):
    def __init__(self, rs: str) -> None:
        self.ter = rs

    @PrintLog("useless arg that I'm passing to __init__")
    def baz(self) -> None:
        print('inside baz')

Опять же, это работает, но я не хочу передавать какие-либо аргументы декоратору.

tl;dr: этот вопрос в python 3.x.

Помощь приветствуется!

2 ответа

Декораторы класса принимают функцию в качестве субъекта внутри __init__метод (отсюда и сообщение журнала), поэтому ваш код декоратора должен выглядеть так:

      class PrintLog(object):

    def __init__(self, function):
        self.function = function

    def __call__(self):
        @wraps(self.function)
        def wrapped(*args):
            print('I am a log')
            return self.function(*args)
        return wrapped

Извините, если это не работает, я отвечаю на своем мобильном устройстве.

РЕДАКТИРОВАТЬ:

Итак, это, вероятно, не то, что вы хотите, но вот способ сделать это:

      from functools import update_wrapper, partial, wraps


class PrintLog(object):

    def __init__(self, func):
        update_wrapper(self, func)
        self.func = func
    
    def __get__(self, obj, objtype):
        """Support instance methods."""
        return partial(self.__call__, obj)

    def __call__(self, obj, *args, **kwargs):
        @wraps(self.func)
        def wrapped(*args):
            print('I am a log')
            return self.func(*args)
        return wrapped(obj, *args)


class foo(object):
    def __init__(self, rs: str) -> None:
        self.ter = rs

    @PrintLog
    def baz(self) -> None:
        print('inside baz')


bar = foo('2')
print('running bar.baz()')
bar.baz()

Декоратор должен иметь __get__метод определен, потому что вы применяете декоратор к методу экземпляра. Как дескриптор будет иметь контекст fooпример?

Ссылка: Декорирование методов класса Python — как передать экземпляр декоратору?

Вы упускаете большую картину.

      @decorator
def foo(...):
   function_definition

почти идентичен (за исключением некоторой внутренней деформации)

      temp = foo
foo = decorator(temp)

Неважно, что такое декоратор, если он может действовать как функция.

Ваш пример эквивалентен:

      baz = PrintLog("useless thing")(<saved defn of baz>)

Поскольку это класс, PrintLog(...)создает экземпляр PrintLog. Этот экземпляр имеет __call__метод, поэтому он может действовать как функция.

Некоторые декораторы предназначены для приема аргументов. Некоторые декораторы не принимают аргументы. Некоторые, как @lru_cache, являются кусочками магии Python, которые смотрят, является ли «аргумент» функцией (поэтому декоратор используется напрямую) или числом/None, так что он возвращает функцию, которая затем становится декоратором.

Другие вопросы по тегам