Python装饰器的作用是使函数包装与方法包装(一个函数,接受函数并返回其增强函数)变得更容易阅读和理解。最初的使用场景是在方法定义的开头能够将其定义为类方法或静态方法。
不使用装饰器的代码如下所示
类方法不用装饰器的写法
class WithoutDecorators:
def some_static_method():
print("this is static method")
some_static_method = staticmethod(some_static_method)
def some_class_method(cls):
print("this is class method")
some_class_method = classmethod(some_class_method)
函数不用装饰器的写法
def decorated_function():
pass
decorated_function = some_decorator(decorated_function)
如果用装饰器语法重写的话,代码会更简短,也更容易理解:
类方法使用装饰器的写法
class WithDecorators:
@staticmethod
def some_static_method():
print("this is static method")
@classmethod
def some_class_method(cls):
print("this is class method")
函数使用装饰器的写法
@some_decorator
def decorated_function():
pass
装饰器通常是一个命名的对象,在装饰函数时接受单一参数,并返回另一个可调用(callable)对象,任何实现了__ call __
方法的可调用对象都可以用作装饰器,它们返回的对象往往也不是简单的函数,而是实现了自己的__ call __
方法的更复杂的类的实例。
任何函数都可以用作装饰器,Python没有规定装饰器的返回类型。所以使用单一参数但不返回可调用对象的函数用作装饰器,在语法上是完全有效的。如果调用这样装饰过的对象就会报错。
def mydecorator(function):
def wrapped(*args, **kwargs):
# 在调用原始函数之前,做点什么
result = function(*args, **kwargs)
# 在函数调用之后,做点什么,
# 并返回结果
return result
# 返回wrapper作为装饰函数
return wrapped
mydecorator传入的function是函数,在mydecorator中定义了一个函数wrapped,在wrapped函数中args和kwargs参数是原函数function的参数,装饰器使用wrapped来对函数进行修饰,所以装饰器返回的也是wrapped
class DecoratorAsClass:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
# 在调用原始函数之前,做点什么
result = self.function(*args, **kwargs)
# 在调用函数之后,做点什么,
# 并返回结果
return result
在这个类中self.function为需要修饰的函数,在__call__方法中定义对self.function的修饰
在实际代码中通常需要使用参数化的装饰器。如果用函数作为装饰器的话,需要用到第二层函数嵌套包装。
def repeat(number=3):
def actual_decorator(function):
def wrapper(*args, **kwargs):
result = None
for _ in range(number):
result = function(*args, **kwargs)
return result
return wrapper
return actual_decorator
在这里构建的是多次重复执行的装饰函数,number参数为重复次数,默认值是3
我们来测试一下参数化的装饰器
使用装饰器的常见问题是使用装饰器时不保存函数元数据,尤其是文档字符串和原始函数名。装饰器组合创建了一个新函数,并返回一个新对象,但却完全没有考虑原始函数的标识。这将会使得调试这样装饰过的函数更加困难,也会破坏可能用到的大多数自动生成文档的工具,因为无法访问原始的文档字符串和函数签名。我们来看一下细节。假设我们有一个虚设的(dummy)装饰器,仅有装饰作用,还有其他一些被装饰的函数:
def dummy_decorator(function):
def wrapped(*args, **kwargs):
"""包装函数内部文档。"""
return function(*args, **kwargs)
return wrapped
@dummy_decorator
def function_with_important_docstring():
"""这是我们想要保存的重要文档字符串。"""
Pass
解决这个问题的正确方法是使用functools模块内置的wraps()装饰器
from functools import wraps
def preserving_decorator(function):
@wraps(function)
def wrapped(*args, **kwargs):
"""包装函数内部文档。"""
return function(*args, **kwargs)
return wrapped
@preserving_decorator
def function_with_important_docstring():
"""这是我们想要保存的重要文档字符串。"""
pass
测试效果如下