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

Декораторы класса против декораторов

В python есть два способа объявления декораторов:

Основанный на классе

class mydecorator(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        self.f(*k, **kw)
        # after f actions

Основанная на функциях

def mydecorator(f):
    def decorator(*k, **kw):
        # before f actions
        f(*k, **kw)
        # after f actions

    return decorator          

Есть ли разница между этими объявлениями? В каких случаях следует использовать каждый из них?

4b9b3361

Ответ 1

Если вы хотите сохранить состояние в декораторе, вы должны использовать класс.

Например, это не работает

def mydecorator(f):
    x = 0 
    def decorator():
        x += 1 # x is a nonlocal name and cant be modified
        return f(x)
    return decorator 

Существует много обходных решений, но самым простым способом является использование класса

class mydecorator(object):
    def __init__(self, f):
        self.f = f
        self.x = 0

    def __call__(self, *k, **kw):
        self.x += 1
        return f(self.x)

Ответ 2

Когда вы создаете вызывающий вызов, возвращающий другой вызываемый, функциональный подход проще и дешевле. Существуют два основных отличия:

  • Функциональный подход работает автоматически с помощью методов, а если вы используете свой подход к классу, вам придется читать дескрипторы и определять метод __get__.
  • Классовый подход упрощает управление. Вы можете использовать закрытие, особенно в Python 3, но классный подход обычно предпочтительнее для сохранения состояния.

Кроме того, функциональный подход позволяет вернуть исходную функцию после ее изменения или сохранения.

Однако декоратор может возвращать нечто, отличное от вызываемого, или нечто большее, чем вызываемое. С помощью класса вы можете:

  • Добавьте методы и свойства к украшенному вызываемому объекту или выполните операции над ними (uh-oh).
  • Создавайте дескрипторы, которые действуют особым образом при размещении в классах (например, classmethod, property)
  • Используйте наследование для реализации похожих, но разных декораторов.

Если у вас есть сомнения, спросите себя: хотите ли вы, чтобы ваш декоратор возвращал функцию, которая действует точно так же, как функция? Используйте функцию, возвращающую функцию. Вы хотите, чтобы ваш декоратор возвращал пользовательский объект, который делает что-то большее или что-то иное, чем функция? Создайте класс и используйте его как декоратор.

Ответ 3

На самом деле нет "двух способов". Существует только один способ (определить вызываемый объект) или так много способов, как в python, сделать вызываемый объект (это может быть метод другого объекта, результат выражения лямбда, "частичный" объект, все, что есть вызываемый).

Определение функции - самый простой способ сделать вызываемый объект и, как самый простой, является, вероятно, лучшим в большинстве случаев. Использование класса дает вам больше возможностей для четкого кодирования более сложных случаев (даже в самых простых случаях это выглядит довольно элегантно), но не так очевидно, что он делает.

Ответ 4

Нет, есть (более) два способа сделать вызываемые объекты. Один из них - это def функция, которая, очевидно, может быть вызвана. Другим является определение метода __call__ в классе, который сделает экземпляры его вызываемыми. А сами классы являются вызываемыми объектами.

Декоратор - это не что иное, как вызываемый объект, который предназначен для, чтобы принять функцию в качестве единственного аргумента и вернуть что-то вызываемое. Следующий синтаксис:

@decorate
def some_function(...):
    ...

Это всего лишь несколько приятный способ написания:

def some_function(...):
    ...

some_function = decorate(some_function)

Представленный на основе класса пример не является функцией, которая принимает функцию и возвращает функцию, которая является стандартным дизайнером ванили для болота, это класс, который инициализируется функцией, чьи экземпляры вызываются. Для меня это немного странно, если вы на самом деле не используете его как класс (есть ли у него другие методы? Изменяется ли его состояние? Вы делаете несколько экземпляров этого, которые имеют общее поведение, инкапсулированное классом?). Но нормальное использование вашей украшенной функции не скажет разницы (если только это не является особенно инвазивным декоратором), так что делайте то, что вам кажется более естественным.

Ответ 5

Позвольте просто проверить это!

test_class = """
class mydecorator_class(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        print 'hi class'
        self.f(*k, **kw)
        print 'goodbye class'
        # after f actions

@mydecorator_class
def cls():
    print 'class'

cls()
"""

test_deco = """
def mydecorator_func(f):
    def decorator(*k, **kw):
        # before f actions
        print 'hi function'
        f(*k, **kw)
        print 'goodbye function'
        # after f actions
    return decorator

@mydecorator_func
def fun():
    print 'func'

fun()
"""

if __name__ == "__main__":
    import timeit
    r = timeit.Timer(test_class).timeit(1000)
    r2 = timeit.Timer(test_deco).timeit(1000)
    print r, r2

У меня есть результаты вроде этого: 0.0499339103699 0.0824959278107

Это означает, что class deco в 2 раза быстрее?