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