Шаг 165.
Python: сборник рецептов. Метапрограммирование. Применение декораторов к методам класса и статическим методам

    На этом шаге мы рассмотрим решение этой задачи.

Задача

    Вы хотите применить декоратор к методу класса или статическому методу.

Решение

    Применение декораторов к методам класса и статическим методам выполняется обычным способом, но нужно убедиться, что ваши декораторы применяются к методам перед @classmethod и @staticmethod. Например:

>>> import time
>>> from functools import wraps
>>> # Простой декоратор
>>> def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return r
    return wrapper

>>> # Класс, иллюстрирующий применение декоратора к различным типам методов
>>> class Spam:
    @timethis
    def instance_method(self, n):
        print(self, n)
        while n > 0:
            n -= 1

    @classmethod
    @timethis
    def class_method(cls, n):
        print(cls, n)
        while n > 0:
            n -= 1

    @staticmethod
    @timethis
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1

>>> 

    Получившиеся метод класса и статический метод должны работать как обычно, но с добавлением дополнительной функциональности:

>>> s = Spam()
>>> s.instance_method(10000000)
<__main__.Spam object at 0x00000107349F8400> 10000000
1.2668874263763428
>>> Spam.class_method(10000000)
<class '__main__.Spam'> 10000000
1.2826385498046875
>>> Spam.static_method(10000000)
10000000
1.2948384284973145
>>> 


Обсуждение

    Если вы перепутаете порядок применения декораторов, то получите ошибку. Например, если вы сделаете так:

class Spam:
    . . .
    @timethis
    @staticmethod
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1
то статический метод вызовет ошибку:
>>> Spam.static_method(1000000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "timethis.py", line 6, in wrapper start = time.time()
TypeError: 'staticmethod' object is not callable 
>>>
Проблема в том, что @classmethod и @staticmethod на самом деле не создают объекты, которые можно вызывать напрямую. Вместо этого они создают специальные объекты дескрипторов, как описано на 138 шаге. Так что если вы попытаетесь использовать их в качестве функций в другом декораторе, ваш декоратор упадет с ошибкой. Убедитесь, что эти декораторы идут первыми по порядку в списке декораторов, и это решит проблему.

    Есть ситуация, где этот рецепт имеет чрезвычайную важность: при определении классов и статических методов в абстрактных базовых классах, как описано на 141 шаге. Например, если вы хотите определить метод абстрактного класса, то можете использовать такой код:

from abc import ABCMeta, abstractmethod

class A(metaclass=ABCMeta):
    @classmethod 
    @abstractmethod 
    def method(cls): 
        pass

    В этом фрагменте порядок @classmethod и @abstractmethod имеет значение. Если вы поменяете местами эти декораторы, все поломается.

    На следующем шаге мы рассмотрим написание декораторов, которые добавляют аргументы обернутым функциям.




Предыдущий шаг Содержание Следующий шаг