装饰器是Python中最强大且优雅的特性之一,它允许你在不修改函数或类源代码的情况下,动态地扩展它们的功能。装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。对应到java,其形式和注解切面非常相似。
一、装饰器的基本使用
下面用一个例子解释装饰器
def log(fun):
def wrapper(*args, **kwargs):
print("方法调用前")
result = fun(*args, **kwargs)
print("方法调用后")
return result
return wrapper
@log
def hello():
print("Hello,World")
if __name__ == '__main__':
hello()
运行结果:
方法调用前
Hello,World
方法调用后
上面的代码执行了hello方法,hello方法的@log注解实际上相当于:
hello = log(hello)
hello()
log方法返回的方法实际上已经变成了wrapper方法,而wrapper方法接收的参数是*args, **kwargs
,所以它可以接受任意参数。
来看下它的等价代码:
def log(fun):
def wrapper(*args, **kwargs):
print("方法调用前")
result = fun(*args, **kwargs)
print("方法调用后")
return result
return wrapper
def hello():
print("Hello,World")
if __name__ == '__main__':
hello = log(hello)
hello()
运行结果和装饰器的运行结果是一样的。
二、装饰器传参
如果装饰器方法本身需要一些参数控制装饰器的行为,该如何传参呢?比如以上的log装饰器,我需要传一个参数level,形式如下:
@log(level="INFO")
def hello():
print("Hello,World")
打印日志的时候需要将该字符串放到日志前面,如何实现?
这里需要用到三层嵌套,修改后的代码如下
def log(level):
def decorator(fun):
def wrapper(*args, **kwargs):
print(f"{level}:方法调用前")
result = fun(*args, **kwargs)
print(f"{level}:方法调用后")
return result
return wrapper
return decorator
@log(level="INFO")
def hello():
print("Hello,World")
if __name__ == '__main__':
hello()
运行结果:
INFO:方法调用前
Hello,World
INFO:方法调用后
此时的hello()
方法调用等价于以下代码:
hello = log(level="INFO")(hello)
hello()
完整的等价代码如下所示:
def log(level):
def decorator(fun):
def wrapper(*args, **kwargs):
print(f"{level}:方法调用前")
result = fun(*args, **kwargs)
print(f"{level}:方法调用后")
return result
return wrapper
return decorator
# @log(level="INFO")
def hello():
print("Hello,World")
if __name__ == '__main__':
hello = log(level="INFO")(hello)
hello()
三、函数签名变更问题
装饰器传参的问题也解决了,接下来看看装饰器引发的函数签名变化问题,运行以下代码,hello方法的__name__
属性还是hello吗?
def log(level):
def decorator(fun):
def wrapper(*args, **kwargs):
print(f"{level}:方法调用前")
result = fun(*args, **kwargs)
print(f"{level}:方法调用后")
return result
return wrapper
return decorator
@log(level="INFO")
def hello():
print("Hello,World")
if __name__ == '__main__':
print(hello.__name__)
输出:
wrapper
没错,hello的方法名已经变成了wrapper,这对于某些依赖函数签名的代码执行就会出错。
首先想到的解决方案就是复制hello方法的__name__
到wrapper方法。。实际上使用内置的functools
模块可以解决这个问题,只需要在wrapper方法上使用@functools.wraps(log)
标记一下即可,来看下它的使用:
import functools
def log(level):
def decorator(fun):
@functools.wraps(log)
def wrapper(*args, **kwargs):
print(f"{level}:方法调用前")
result = fun(*args, **kwargs)
print(f"{level}:方法调用后")
return result
return wrapper
return decorator
@log(level="INFO")
def hello():
print("Hello,World")
if __name__ == '__main__':
print(hello.__name__)
输出:
log
参考文档:https://liaoxuefeng.com/books/python/functional/decorator/index.html
END.
注意:本文归作者所有,未经作者允许,不得转载