python的装饰器
按照这个顺序去循序渐进理解装饰器:高阶函数->柯里化->装饰器
在python里,函数可以是对象,可以作为普通变量、参数、返回值等等。
所以高阶函数定义:
1.输入一个或多个函数作为参数;
2.输出一个函数。
例如样式:
y = g(f(x))
闭包就是一个高阶函数:
def counter(base): def inc(step=1): nonlocal base # 注意这里要使用nonlocal关键字,参见函数作用域那一篇 base += step return base return inc
python内置的很多函数都是高阶函数,例如:sorted(),filter(),map()函数等。
sorted():
>>> l1 = [0,1,2,3,4] >>> sorted(l1, key = lambda x:x+6) #sorted(iterable[,key][,reverse])返回新列表 [0, 1, 2, 3, 4] >>> l1.sort(key = lambda x:x+6) # 就地对原列表修改 >>> l1 [0, 1, 2, 3, 4]
filter():
>>> filter(lambda x:x%3 == 0, l1) # filter(function, iterable)过滤可迭代对象对元素,返回一个生成器。将lambda下声明的匿名函数作为参数传进filter() <filter object at 0x114e246d8> >>> list(filter(lambda x:x%3 == 0, l1)) # 将生成器转化为列表 [0, 3] >>> temp = filter(lambda x:x%3 == 0, l1) # 输出生成器里的元素 >>> for i in temp: print(i) 0 3
map():
>>> map(lambda x:x*2+1,range(5)) # map(function, *iterable) <map object at 0x1035009e8> >>> list(map(lambda x:x*2+1,range(5))) # 转列表 [1, 3, 5, 7, 9] >>> dict(map(lambda x:(x%5,x),range(10))) # 转字典,里面有字典的key去重 {0: 5, 1: 6, 2: 7, 3: 8, 4: 9}
柯里化Currying(从高阶函数开始向装饰器过渡):
将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
简单表达为:z = f(x,y) ==> z = f(x)(y)
同样是举一个简单的例子:
# 原函数 def add(x, y): return x+y # 柯里化后的函数(也是闭包) def add(x): def _add(y): return x+y return _add # 调用 add(5)(6) #输出11
了解了以上两个概念,我们跳转到一个具体的场景中:
假设在工作中遇到一个针对add()函数的需求:增强add()函数的功能,使其能够输出被调用时的参数信息。
# 原函数 def add(x, y); return x+y # 增加信息输出功能 def add(x, y); print("call add, x + y") # 日志输出到控制台 return x+y
我们发现上面方法虽然完成需求,但是输出信息的非业务功能代码不该放在加法运算的业务功能代码中,耦合度很高。
为了将业务功能代码和非业务功能代码分离开来,我们想出办法:将两者独立开来,并将业务功能函数作为参数传入非业务功能函数。
# 业务功能 def add(x ,y): return x+y # 非业务功能 def logger(fn): print('begin') x = fn(4, 5) print('end') return x # 完成需求 print(logger(add))
上面的方法能很好的解决业务功能代码和非业务功能代码分离开来的问题,但是我们也会发现业务功能函数(被装饰的函数)的参数在非业务功能函数(装饰函数)的调用传参时是一个问题:
logger(fn)里fn(4, 5)是写死的。
为了能够更灵活地传参,我们使用*args,**kwargs来表示原函数的参数。
# 原函数(不做任何改动) def add(x, y): return x+y # 增加的非业务功能部分(将原函数和原函数里的参数一并作为新函数的参数传递进去);*args和**kwargs的知识点就不在这里展开了 def logger(fn, *args, **kwargs): print('begin') x = fn(*args, **kwargs) print('end') return x # 调用 print(logger(add,5,y=10))
上面的方法已经能满足我们的需求,并解决了业务代码和非业务代码混在一起和参数传递的问题,但是我们发现在函数调用的时候,我们的写法是调用logger()函数( logger(fn,*args,**kwargs) )来实现业务和非业务的功能。怎样能够是调用add()函数来实现功能呢?可以联想到上面提到的柯里化方法:
logger(fn,*args,**kwargs) --柯里化--> logger(fn)(*args,**kwargs) --将logger(fn)赋给add(),调用方式即可以写为 --> add(*args,**kwargs)
def add(x, y): return x+y # 将logger()函数柯里化 def logger(fn): def wrapper(*args, **kwargs): print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper # 直接add()函数调用;第一句的铺垫在下面用语法糖代替 add = logger(add) print(add(5,y=10))
到这,就已经形成了一个装饰器的雏形,再加上装饰器的语法糖和调换两个函数的位置后就是一个完整的装饰器(包含装饰函数和被装饰函数):
def logger(fn): def wrapper(*args, **kwargs): print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper @logger # 等价于 add = logger(add);调换顺序是因为在这里要先声明装饰器函数,然后根据这句语法糖,装饰紧跟在@logger后面的(被装饰的)原函数 def add(x, y): return x+y # 调用 print(add(5,y=10))
我们根据上面的形式,做一个简单的归纳:
装饰器:
1.它是一个函数(logger())。
2.被装饰的函数(add())作为它的形参(logger(fn))。
3.返回值也是一个函数,是包装了原函数的参数的函数(wrapper(*args,**kwargs))。
4.语法糖:使用@function_name(装饰器函数名)的方式简化调用(@logger)。
装饰器是高阶函数,同时装饰器起着对传入函数的功能的装饰作用。