装饰器的理解
以下内容整理于如何理解Python装饰器? - 刘志军的回答 - 知乎和廖雪峰老师Python教程。
装饰器
装饰器本质上是一个Python函数,装饰器的返回值也是一个函数对象,它可以在不改变其他函数的代码的前提下,实现动态的增加其他函数的功能。它经常用于有切面需求的场景,比如插入日志、测试等。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
一个例子
def func1():
print("i am func 1")
我们在做测试时,希望可以记录下该函数的执行日志,于是可以修改代码:
def func1():
print("i am func1")
logging.info("func1 is running")
但是函数func2(),函数func3()等函数也有同样的需要,如果我们仍采用这种方法,不仅低效,写了大量的重复且与原函数功能不相关的代码,于是我们可以将日志代码抽离出来,如下:
def logging(func):
logging.info("%s is running" % func.__name__)
func()
def func2():
print("i am func2")
logging(func2)
这样做可以实现我们预期功能,但是缺点很明显,我们破坏了原始代码的逻辑结构。这时候,我们可以使用装饰器来实现同样的功能,而不破坏原始逻辑结构。
简单的装饰器
def logging(func):
def wrapper(*args, **kwargs):
logging.info("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
def func3():
print("i am func3")
func3 = logging(func3)
func3()
在上面的代码中,logging()函数就是一个装饰器,用以实现我们测试时的需求。func3()函数则是我们实现原始功能的函数。这样我们就将实现日志功能的代码抽离了出来。
进一步完善
“@”符号是装饰器的语法糖,可在定义函数时使用,避免后面再一次的赋值操作。
def logging(func):
def wrapper(*args, **kwargs):
logging.info("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
@logging
def func3():
print("i am func3")
@logging
def func4():
print("i am a func4")
func3()
func4()
像上面这样,我们则可以省略“func3 = logging(func3)”这一赋值操作,直接调用func3()函数即可实现同样的功能。
对以上代码的分析理解
logging()函数是一个装饰器,它返回的是一个函数,因此func3变量在执行“@logging”(func3 = logging(func3))后指向了新的函数logging(func3),而且logging()函数中的局部变量func现在指向func3变量在定义时指向的函数。因此在调用func3()函数时将执行新函数logging(func3)。
对比一下
如果装饰器函数这样定义:
def logging(func):
def wrapper(*args, **kwargs):
logging.info("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper() # 这里做了变动
那么,在执行“func3 = logging(func3)”后,func3变量指向的就是wrapper()执行后的返回值。因此可以看出,函数名后的“()”是函数运行的标志。