一前言
环境: win10 python3.10
二 函数中的函数
如果定义了一个函数A,现在想在不影响函数A原先功能的情况下,新增加一些额外的功能,怎么办,下面是一个例子
如上,本来原先执行test_except那句话只会打印over那句话,但现在执行test_except却会输出一些另外的东西
这其中有个巧妙地东西就同一个变量名所指的对象不同
原先的test_except指向的就是我们所定义的包含try语句的函数
当我们执行de()并赋值给同名变量test_except,在看下
如上,这时except依然指向的是一个函数,只不过不再试原来的函数,而是另一个函数de_in
三 装饰器
1 普通装饰器
装饰器就是返回值为另一个函数的函数,只不过它的采用形式有点特别
严格来说,上面装饰器函数de的定义还差一步,这个最后面再说
执行结果
如上,de为装饰器,test_except为被装饰的函数
结果和之前采用变量赋值的方法来运行几乎是一样的,可以认为这两种改变函数功能的方法几乎是等价的
这里有个细节,de function code 是在 start之前输出的。也就说装饰器函数不是在被装饰函数运行时才开始执行的,而是在被装饰函数在加载的时候就开始执行
在上一小变量赋值部分就可以看到,在执行test_except=de(test_except)时,de函数的主体部分就开始执行了(其中的子函数还为开始执行),而最终的test_except函数(也就是de函数的子函数)还未执行
也就是说,把@de 放在 def test_except的前面,就大致相当于执行下面代码
test_except = de(test_except)
如果被装饰函数需要参数,装饰器函数定义加上形参即可,如下
严格来说,上面装饰器函数de的定义还差一步,这个最后面再说
2 多个装饰器
函数可被多个装饰器装饰
严格来说,上面装饰器函数的定义还差一步,这个最后面再说
执行结果
如上,两个装饰器装饰的函数,其过程大致是这样
@db
@de
就大致相当于执行
test_except = db(de(test_except))
其中de(test_except)先执行,打印出de fuction code,并返回de_in作为结果。然后上面就变成这样
test_except = db(de_in)
这时候,右边先执行,打印出db function code ,并返回db_in作为结果,变化成这样
test_except = db_in
这时候我们输入test_except() ,就会执行db_in,打印出db_in function code
然后de_in中,它里面还有一个函数会执行,即上一步参数中的函数de_in,打印出de_in function code
然后在执行de_in中,它里面还有一个函数会执行,即第一步的test_except函数
最后打印出over
3 装饰器传入参数
上面的装饰器使用时都没有参数,直接是@decorator的形式,如果要给decorator即装饰器传入参数,则需要把普通装饰器再套在一层函数里面
严格来说,上面装饰器函数de的定义还差一步,这个最后面再说
执行结果
上图的@dc(8,9)大致相当于执行
test_except = dc(8,9)(tes_except)
之前的普通装饰器是这样
test_except=de(test_except)
其实要点就在于,用dc(8,9)来得到de这个函数。即在普通装饰器基础上,用一个函数来得到de这个函数对象(即de的函数定义)
来看看具体带参数装饰器的执行过程
test_except = dc(8,9)(tes_texcept)
先执行dc(8,9),会打印出dc function code,并返回decorator_de这个函数。就变成这样
test_except = decorator_de(test_except)
这不又是普通装饰器的的第一步了
4 完整的装饰器的定义
之前多次提到,装饰器的定义还差最后一步,也说过变量赋值和@ 这两种方式大致是相同的。
这些所说的就是函数的属性__name__(也就是函数的名称问题)
首先我们用变量赋值的方法,来看下貌似改增加了额外功能的那个函数的前后的名称
首先看下赋值前,函数的__name__属性
这个结果应该很好理解
再看下赋值后的函数的__name__属性
这个前面也讲过,test_except这个名称虽然没有变化,但她执行的对象发生了变化,现在指向了de_in这个函数。所以他的__name__变成了de_in
如果是用之前的装饰器定义去运行,也会发生相同的结果,__name__最终也会变成de_in
这样看上去功能也没啥影响,但有时一些依赖函数签名的地方就会报错,以防万一,在使用@decorator这种形式时,我们需要把__name__的值变回为原来最开始的函数名称(即被装饰的函数名)
所以严格的装饰器定义是这样
import functools
def decorator(f):
xxxxx
# 在装饰器增加下面这行,改回__name__的值
@functools.wraps(f)
def wrapper(*args, **kw):
xxxx
f(*args, **kw)
return wrapper
# 上面的@functools.wraps(f)也可以替换成wrapper.__name__ = func.__name__
现在我们来试一下