• 9.10 为类和静态方法提供装饰器
    • 问题
    • 解决方案
    • 讨论

    9.10 为类和静态方法提供装饰器

    问题

    你想给类或静态方法提供装饰器。

    解决方案

    给类或静态方法提供装饰器是很简单的,不过要确保装饰器在 @classmethod@staticmethod 之前。例如:

    1. import time
    2. from functools import wraps
    3.  
    4. # A simple decorator
    5. def timethis(func):
    6. @wraps(func)
    7. def wrapper(*args, **kwargs):
    8. start = time.time()
    9. r = func(*args, **kwargs)
    10. end = time.time()
    11. print(end-start)
    12. return r
    13. return wrapper
    14.  
    15. # Class illustrating application of the decorator to different kinds of methods
    16. class Spam:
    17. @timethis
    18. def instance_method(self, n):
    19. print(self, n)
    20. while n > 0:
    21. n -= 1
    22.  
    23. @classmethod
    24. @timethis
    25. def class_method(cls, n):
    26. print(cls, n)
    27. while n > 0:
    28. n -= 1
    29.  
    30. @staticmethod
    31. @timethis
    32. def static_method(n):
    33. print(n)
    34. while n > 0:
    35. n -= 1

    装饰后的类和静态方法可正常工作,只不过增加了额外的计时功能:

    1. >>> s = Spam()
    2. >>> s.instance_method(1000000)
    3. <__main__.Spam object at 0x1006a6050> 1000000
    4. 0.11817407608032227
    5. >>> Spam.class_method(1000000)
    6. <class '__main__.Spam'> 1000000
    7. 0.11334395408630371
    8. >>> Spam.static_method(1000000)
    9. 1000000
    10. 0.11740279197692871
    11. >>>

    讨论

    如果你把装饰器的顺序写错了就会出错。例如,假设你像下面这样写:

    1. class Spam:
    2. @timethis
    3. @staticmethod
    4. def static_method(n):
    5. print(n)
    6. while n > 0:
    7. n -= 1

    那么你调用这个静态方法时就会报错:

    1. >>> Spam.static_method(1000000)
    2. Traceback (most recent call last):
    3. File "<stdin>", line 1, in <module>
    4. File "timethis.py", line 6, in wrapper
    5. start = time.time()
    6. TypeError: 'staticmethod' object is not callable
    7. >>>

    问题在于 @classmethod@staticmethod 实际上并不会创建可直接调用的对象,而是创建特殊的描述器对象(参考8.9小节)。因此当你试着在其他装饰器中将它们当做函数来使用时就会出错。确保这种装饰器出现在装饰器链中的第一个位置可以修复这个问题。

    当我们在抽象基类中定义类方法和静态方法(参考8.12小节)时,这里讲到的知识就很有用了。例如,如果你想定义一个抽象类方法,可以使用类似下面的代码:

    1. from abc import ABCMeta, abstractmethod
    2. class A(metaclass=ABCMeta):
    3. @classmethod
    4. @abstractmethod
    5. def method(cls):
    6. pass

    在这段代码中,@classmethod@abstractmethod 两者的顺序是有讲究的,如果你调换它们的顺序就会出错。

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p10_apply_decorators_to_class_and_static_methods.html