Python笔记 · 函数装饰器(Decorators)
Posted bluishglc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python笔记 · 函数装饰器(Decorators)相关的知识,希望对你有一定的参考价值。
Python的装饰器语法与面向切面编程(AOP)
的设计意图是高度一致的,多应用于日志、事务处理等具有“横切”性质编程场合,与Java中的动态代理以及设计模式中的代理模式
类似(与装饰器模式有相似之处,但有差异),可以认为是Python在语言级别上内置实现了代理模式或面向切面编程模式,也属于一种语法糖。
考虑这样一个普通的函数:
def my_fun():
print("This is a function")
如果我们想为这个函数,也包括所有其他函数添加一个自动日志的功能,以便记录一下函数调用前后的时间,这是一个典型的“横切”场景,实际上,更加具有实际应用价值的一个案例是:在方法调用前启动一个事务,在方法执行完毕后提交一个事务,此类是更加典型的案例。这时我们为了方便,以日志功能来举例。
为了能实现这个通用的,具有横切功能的函数,我们需要引入高级函数特性,具体实现如下:
def log(func):
def wrap(*args, **kw):
print('before call %s():' % func.__name__)
result = func(*args, **kw)
print('after call %s():' % func.__name__)
return result
return wrap
首先,我们要注意一下这个log的函数的整体“框架”:它先在函数体内定义了一个子函数warp,然后log函数最终返回的其实是wrap函数。wrap函数是实现包裹逻辑的地方。之所以要嵌套声明一个wrap函数,而不是在log中直接实现包裹逻辑的主要原因在于:包裹的逻辑(也就是打日志的代码)不是在“包裹”时执行,而是必须要“推迟”到目标函数调用时“伴随”执行,这就要求从函数完成包裹到函数调用前要有一个“中间载体”,其即包含了中间逻辑,同时在调用行为上还要与目标函数无异,实际上,此时的“中间载体”在Java等语言中就是一个“代理类”(动态代理或通过cglib修改字节码得到代理类),而在Python中,以一个函数形式(就是这里的wrapp)包裹是一种更轻量和边界方法,这就是为什么不能在log中直接实现包裹逻辑,而是要下放到一个子的包裹函数,并反回该函数的原因。接下来,我们就看一下最后的执行效果:
# 完成包裹逻辑定义
my_fun = log(my_fun)
# 调用目标函数的同时也执行了包裹逻辑
# 此时的my_fun已经不是开始时定义的那个my_fun了,而是wrapper
my_fun()
至此,“朴素版”的装饰器函数log就算实现完成了,由于类似的装饰场景普遍存在,Python在语法层面上提升了对这一模式的支持力度,使用了@wrap_fucniton_name
的语法来简化了包裹函数和目标函数之间的粘合操作。所以上述代码可以简化为:
def log(func):
def wrap(*args, **kw):
print('before call %s():' % func.__name__)
result = func(*args, **kw)
print('after call %s():' % func.__name__)
return result
return wrap
@log
def my_fun():
print("This is a function")
my_fun()
我们在my_fun方法前添加@log
,就等同于执行了my_fun = log(my_fun)
这一操作,也就是自动完成了被包裹函数和包裹函数之间的“注册/关联”操作,这样无疑是更加简洁和优雅的。
此时Python的函数装饰器机制基本就介绍完了,但是,通常人们会因为这样一个小问题导致一些麻烦,就是此时的my_fun
已经不是开始时定义的那个my_fun
了,因为此时使用print(my_fun.__name__)
得到的函数名是warp
而不是my_fun
,这会导致在执行时需要判断函数名的程序(类似if xxx.__name__ == 'xxx'
这样的代码)执行失败,虽然我们确实可以在包裹函数函数中手动修正命名上的偏差,类似wrapper.__name__ = func.__name__
这样,但是Python中有更好的更优雅的解决方案functools.wraps
:
def log(func):
@functools.wraps(func)
def wrap(*args, **kw):
print('before call %s():' % func.__name__)
result = func(*args, **kw)
print('after call %s():' % func.__name__)
return result
return wrap
@log
def my_fun():
print("This is a function")
my_fun()
print(my_fun.__name__)
由此可以看出,使用了@functools.wraps(func)
后,wrap函数的__name__
属性被自动修正了,这也是为什么@functools.wraps(func)
需要func
参数的原因,因为它需要读取目标函数的一些属性并根据它们修正warp函数的属性。
全部输出如下:
before call my_fun():
This is a function
after call my_fun():
my_fun
参考:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584
https://www.runoob.com/w3cnote/python-func-decorators.html
以上是关于Python笔记 · 函数装饰器(Decorators)的主要内容,如果未能解决你的问题,请参考以下文章