廖大的代码如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
装饰器就是一种以函数为参数的函数,定义一定的变换规则,可以将函数进行替换,实现功能扩充,如本段代码中的log
。
装饰器可以在原有函数的基础上,保证其原功能不变的同时,将另一些新功能加入函数,批量定义有相同结构或特性的函数。
我们可以将这个过程理解成为:用wrapper()
替换了被@log
装饰的新定义函数now()
*args
相当于C语言中的一维数组,在python中可以表示任意元组,包括单个数字或字串,**kwargs
(key word argument)相当于二维数组,可以表示字典。因而包含了python中所有的数据类型。所以装饰器具有普适性。
这个过程是用用一个公用的规则来创建一批具有部分类似结构的对象,很容易使人联想到继承。继承语法简单,不涉及高阶函数,可以说相对容易理解,但人们仍然在创立并保留了装饰,背后有其内在逻辑。
OOP语言发展过程当中,继承先于装饰,是为了克服继承的复用性问题而作的。
这个问题主要集中在两个方面:
两类的解决办法如图:
这种方法解决了继承体系的臃肿(如上1),也避免了继承带来的连带关系,降低了代码依赖性(如上2),最终发展成为装饰模式。
通过廖大的这个例子,我们可以更接近装饰器的操作实质。
import functools
def log(text):
def decorator(func):
# @functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('excute')
def now():
print('2020-8-5')
now()
print(f"decorated name: {now.__name__}")
输出结果
excute now():
2020-8-5
decorated name: wrapper
操作过程是,被装饰的函数,按照装饰器的规则进行运算。装饰过程中,作为参数的函数名不变,但返回值为wrapper
。
所以如果对一些特定的情形,应该将变量名换回来,使用语句
@functools.wraps(func)
同一个装饰器可以作用于不同函数的定义。也就实现了复用。
@log('re-excute')
def again():
print('2020-8-6')
again()
print(f"decorated name: {again.__name__}")
输出
re-excute again():
2020-8-6
decorated name: wrapper
经过装饰之后,函数名都发生了改变,那么直接调用是否会产生多态?
经过下面这个尝试,然后猛然意识到只有定义时才可以装饰,也就是说装饰器先于装饰过程。
# def dec(func):
# @functools.wraps(func)
# def wrapper(*args, **kw):
# print('rebuild %s():' % (func.__name__))
# return func(*args, **kw)
# return wrapper
# @dec
# func
考虑到case1 中多函数公用装饰器的问题,可能会出现多义性,导致问题复杂化。我们简单地加以测试,在函数外部直接调用wrapper()
wrapper()
装饰过程是一个高阶函数分层解析的过程。
比如:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
这个过程中,@log
标识符就使得now
作为二阶函数log()
的参数,被里层的wrapper()
调用并包装(调包)并且对外表现为wrapper(now)(*args, **kw)
。
即把log(now)
转化为wrapper(now)
这个“调包”真的可以很巧妙地描述这个装饰过程。
另外,我们还可以使用带参的装饰器举例:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个装饰,将log(text)
替代decorator
,再用log(text)(func)
替代wrapper
,也就是调包两次,最终将func
替换成一个以func
为参数的函数。
这两个例子都指向一个要点,最内层的wrapper
函数名,必须是一个以func
为变量的函数。如果没有这个承接的wrapper
或者实质上的wrapper
,那么这个函数被装饰后成为一个空函数,类型为NoneType
。
如果非要这么用,在调用的时候,不能以函数的规则使用它。
但这种功能缺失并不是说百害无一利。如果需要装饰的函数是无参的,完全可以通过这种紧缩来使得代码更加简洁~ 也可见于文末的示例。我们把这戏称为使能换耗能 。
假如我想将高阶函数降阶,比如将带参的函数阶数减为二阶:
这里尝试将中间层消灭掉,把具有func为参数的函数名的decorator层剪掉
def log(level):
def wrapper(func, *args, **kwargs):
if level == 'info':
print('info log')
elif level == 'error':
print("error log")
# return func(*args, **kwargs)
return wrapper
@log(level='error')
def test():
print('func test')
if __name__ == '__main__':
test
# test() 这个会报错
这个例子中,最终func
会被空值替代,我的猜想是,返回值wrapper
被调包后,函数实质为log(level)(func, *args, **kwargs)
,然而希望得到的返回值是log(level)(func)(*args, **kwargs)
,相当于没有对应参数,因而最终得到了一个空值。尽管这个空值可以作为调用这个装饰器内部功能的句柄。
调用这个被修饰的“函数”的时候,就不能以函数的规则来使用了,否则会报错
使用过程,相当于完成一个变量的自身运算。
另外:
return func(*args, **kwargs)
一句删掉,那么最终结果将现实test
函数并没有被运行。test
函数的内部功能是通过func
实现的。args
和kw
,仍能正常运行。毕竟无参也包括在任意参数里。
*args
相当于C语言中的一维数组,在python中可以表示任意元组,包括单个数字或字串,**kwargs
(key word argument)相当于二维数组,可以表示字典。
当然,总归不推荐这种使用方法(指NoneType装饰器)。
尽管装饰无参函数的时候,具有较好的简洁性:
附一个栗子:
def dec(function):
print("start...")
function()
print("end...")
#修饰器
@dec
def say():
print("say...")
#执行报错:TypeError: 'NoneType' object is not callable
say()
https://www.jianshu.com/p/98f7e34845b5
https://blog.csdn.net/wth_97/article/details/82347803
https://www.jianshu.com/p/998ffc3f2423
https://blog.csdn.net/qq_27093465/article/details/53323187