装饰器
无参装饰器
需求: 一个加法函数, 想增强它的功能,能够输出被调用过程以及调用的参数信息.
1 2 3 4 5 6 7 8 |
def add(x, y): return x + y
或者增加信息输出功能:
def add(x, y): print('call add, x + y') # 日志输出到控制台 return x + y |
该函数已完成要求, 但有以下缺点:
|
更改:
1 2 3 4 5 6 7 8 9 10 |
def add(x, y): return x + y
def logger(fn): print('begin') x = fn(4, 5) print('end') return x
print(logger(add))
|
该函数做到了业务分离,但fn函数调用传参有问题. |
进一步更改:
1 2 3 4 5 6 7 8 9 10 |
def add(x, y): return x + y
def logger(fn, *args, **kwargs): print('begin') x = fn(*args, **kwargs) print('end') return x
print(logger(add, 5, y=60)) |
装饰器语法糖
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
def logger(fn): def wrapper(*args, **kwargs): print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper
@logger # 等价于 add = logger(add)
def add(x, y): return x + y
print(add(6, y=60)) |
@logger就是装饰器语法.
装饰器(无参):
是一个函数;
函数作为它的形参;
返回值也是一个函数;
使用@functionname方式,简化调用;
装饰器和高阶函数:
装饰器是高阶函数, 但装饰器是对传入函数的功能的装饰(即功能增强).
装饰器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import datetime import time def logger(fn): def wrap(*args, **kwargs): print("args={}, kwargs={}".format(args,kwargs)) start = datetime.datetime.now() ret = fn(*args, **kwargs) duration = datetime.datetime.now() - start print("function {} took {}s".format(fn.__name__, duration.total_seconds())) return ret return wrap
@logger # add = logger(add)
def add(x, y): """ This a function of addition.""" print("===call add===========") time.sleep(2) return x + y
print(add(4, y=7))
|
"__name__" 属性用来查看函数名.
理解装饰器:
文档字符串
python的文档:
python是文档字符串Documentation Strings.
在函数语句块的第一行, 且习惯是多行的文本, 所以一般使用三引号.
惯例是首字母大写, 第一行写概述, 空一行, 第三行写详细描述.
可以使用特殊属性 __doc__ 访问这个文档.
1 2 3 4 5 6 7 |
def add(x,y): """This is a function of addition""" a = x+y return x + y
print("func_name = {} doc = {}".format(add.__name__, add.__doc__)) print(help(add)) |
副作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
def func(fn): """This is a function of decorator""" def wrapper(*args, **kwargs): """This is a function of wrapper""" c = fn(*args, **kwargs) return c return wrapper
@func # add = func(add)
def add(x, y): """This is a function od addition.""" return x + y
print(add(4, 5)) print('name: {}, doc: {}'.format(add.__name__, add.__doc__))
# 运行结果: 9 name:wrapper doc:This is a function of wrapper |
原函数对象的属性都被替换了, 而使用装饰器, 需要查看被封装函数的属性.
提供一个函数, 被封装函数属性 – copy -> 包装函数属性.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
def copy_properties(src,dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__
def logger(fn): def wrapper(*args,**kwargs): '''This is a wrapper''' print('before') ret = fn(*args,**kwargs) # add3(4,5,6,y=6,z=5) print('after') return ret
copy_properties(fn,wrapper) return wrapper
@logger
def add(x,y): ''' This is a function return int x int y int ''' ret = x + y return ret
print(add(4, 5), add.__name__, add.__doc__, sep=' ') |
通过copy_properties函数将被包装函数的属性覆盖掉包装函数;
凡是被装饰的函数都需要复制这些属性, 这个函数很通用;
可以将复制属性的函数构建成装饰器函数, 带参装饰器.
带参装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
def copy_properties(src): def _copy(dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ return dst return _copy
def logger(fn): @copy_properties(fn) # wrapper = copy_properties(fn)(wrapper) def wrapper(*args,**kwargs): '''This is a wrapper''' print('before') ret = fn(*args,**kwargs) print('after') return ret return wrapper
@logger
def add(x,y): ''' This is a function return int x int y int ''' ret = x + y return ret
print(add(4, 5), add.__name__, add.__doc__, sep=' ') |
带参装饰器:
获取函数的执行时长, 对时长超过阈值的函数记录一下.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
import datetime, time
def copy(src): def _copy(dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ return dst return _copy
def logger(duration): def _logger(fn): @copy(fn) # wrapper = copy(fn)(wrapper) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() print('so slow') if delta > duration else print('so fast') return ret return wrapper return _logger
@logger(5) # add = logger(5)(add)
def add(x,y): time.sleep(0.5) return x + y print(add(5, 6)) |
带参装饰器:
是一个函数;
函数作为它的形参;
返回值是一个不带参的装饰器函数;
使用@functionname(参数列表)方式调用;
可以看做在装饰器外层又加了一层函数.
将记录的功能提取出来,就可以通过外部提供的函数来灵活控制输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import datetime, time
def copy(src): def _copy(dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ return dst return _copy
def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): def _logger(fn): @copy(fn) # wrapper = copy(fn)(wrapper) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() if delta < duration: func(fn.__name__, duration) return ret return wrapper return _logger
@logger(5)
def add(x,y): time.sleep(1) return x + y print(add(5, 6)) |
functools模块 (1)
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
类似copy_properties功能;
wrapper包装函数, wrapped被包装函数;
元组WRAPPER_ASSIGNMENTS中是需要被覆盖的属性;
'__module__', '__name__', '__qualname__', '__doc__', '__annotations__' (模块名, 名称, 限定名, 文档, 参数注解);
元组WRAPPER_UPDATES中是需要被更新的属性, __dict__属性字典;
增加一个__wrapped__属性, 保留着wrapped函数.
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import datetime, time, functools
def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): def _logger(fn): def wrapper(*args,**kwargs): start = datetime.datetime.now() print(fn) ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() if delta > duration: func(fn.__name__, duration) return ret return functools.update_wrapper(wrapper, fn) # 复制函数属性, 功能类似functools.wraps return _logger
@logger(5) # add = logger(5)(add)
def add(x,y): time.sleep(1) return x + y
print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep=' ') # add.__wrapped与第7行有关. |
使用functools.wraps复制属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import datetime, time, functools
def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): def _logger(fn): @functools.wraps(fn) def wrapper(*args,**kwargs): start = datetime.datetime.now() print(fn) ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() if delta > duration: func(fn.__name__, duration) return ret return wrapper # return functools.update_wrapper(wrapper, fn) # 复制函数属性, 功能类似functools.wraps return _logger
@logger(5) # add = logger(5)(add)
def add(x,y): time.sleep(1) return x + y
print(add(5, 6), add.__name__, add.__dict__, sep=' ') |
参数注解 – Function Annotations
函数定义的弊端与解决办法
python是动态语言, 变量随时可以被赋值, 且能赋值为不同的类型;
python不是静态编译型语言, 变量类型是在运行器决定的;
动态语言很灵活, 但是也有弊端.
1 2 3 4 5 6 7 8 |
def add(x, y): return x + y print(add(5,4)) print(add('hello', 'world')) print(add(4, 'hello')) # 报错, int不能和str直接相加.
# 难发现: 由于不做任何类型检查, 知道运行期问题才显现出来, 或者线上运行时才能暴露出问题; # 难使用: 函数的使用者看到函数的时候, 并不知道你的函数设计, 并不知道应该传入什么类型的数据. |
解决这种动态语言定义的弊端:
1.增加文档Documentation String. (__doc__)
惯例, 非强制标准.
函数定义更新与文档更新未必同步.
2.函数注解.
python3.5引入,
对函数的参数进行类型注解;
只对函数参数做一个辅助说明, 并不对函数参数进行类型检查.
提供第三方工具, 做代码分析, 发现隐藏的BUG.
函数注解信息,保存在 __annotations__属性中.
3.变量注解.
python3.6引入.
i:int = 3
函数注解:
1 2 3 4 5 6 7 8 |
In [1]: def add(x:int, y:int) -> int: ...: return x + y ...:
In [2]: print(add.__annotations__) {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
In [3]: |
业务应用
1.函数参数类型检查;
2.思路:
函数参数的检查,一定是在函数外.
函数应该作为参数,传入到检查函数中.
检查函数拿到函数传入的实际参数,与形参声明对比.
__annotations__属性是一个字典,其中包括返回值类型的声明.假设要做位置参数的判断,无法和字典中的声明对应,使用inspect模块.
3.inspect模块:
提供获取对象信息的函数,可以检查函数和类,类型检查.
inspect模块
signature(callable),获取签名
函数签名包含了一个函数的信息, 包括函数名,它的参数类型,类和名称空间及其他信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
In [3]: import inspect
In [4]: def add(x:int, y:int, *args, **kwargs) -> int: ...: return x + y ...:
In [5]: sig = inspect.signature(add)
In [6]: sig Out[6]: <Signature (x:int, y:int, *args, **kwargs) -> int>
In [7]: sig.parameters # OrderedDict Out[7]: mappingproxy({'args': <Parameter "*args">, 'kwargs': <Parameter "**kwargs">, 'x': <Parameter "x:int">, 'y': <Parameter "y:int">})
In [9]: sig.return_annotation Out[9]: int
In [10]: sig.parameters['y'] Out[10]: <Parameter "y:int">
In [13]: sig.parameters['y'].annotation Out[13]: int
In [15]: sig.parameters['args'] Out[15]: <Parameter "*args">
In [14]: sig.parameters['args'].annotation Out[14]: inspect._empty
In [17]: sig.parameters['args'].annotation Out[17]: inspect._empty
In [18]: sig.parameters['kwargs'] Out[18]: <Parameter "**kwargs">
In [19]: sig.parameters['kwargs'].annotation Out[19]: inspect._empty
|
inspect.isfunction(add), 是否是函数;
isspect.ismethod(add), 是否是类的方法;
inspect.isgenerator(add), 是否是生成器对象;
inspect.isgeneratorfunction(add), 是否是生成器函数;
inspect.isclass(add), 是否是类;
inspect.ismodule(inspect), 是否是模块;
inspect.isbuiltin(print), 是否是内建函数.
...
还有很多is函数,需要的时候查阅inspect模块帮助.
parameter对象:
保存在元组中, 只读.
name为参数的名字.
annotation, 参数的注解, 可能没有定义.
default, 参数的缺省值, 可能没有定义.
empty, 特殊的类, 用来标记default属性或者注释annotation属性.
kind, 实参如何绑定到形参, 就是形参的类型.
POSITIONAL_ONLY, 值必须是位置参数提供;
POSITIONAL_OR_KEYWORD, 值可作为关键字或者位置参数提供.
VAR_POSITIONAL, 可变位置参数, 对应*args.
KEYWORD_ONLY, keyword-only参数, 对应*或者*args之后出现的非可变关键字参数.
VAR_KEYWORD, 可变关键字参数, 对应**kwargs.
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import inspect
def add(x, y:int=7, *args, z, t=10,**kwargs) -> int: return x + y
sig = inspect.signature(add)
print(sig) print(sig.parameters)
for i, item in enumerate(sig.parameters.items()): name, param = item print('name: ',name, 'param: ',param) print(i+1, name, param.annotation, param.kind, param.default) print(param.default is param.empty, end=' ') |
业务应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
import inspect
def check(fn): def wrapper(*args, **kwargs): sig = inspect.signature(fn) params = sig.parameters # OrderedDict values = list(params.values()) for i,p in enumerate(args): param = values[i] if param.annotation is not param.empty and not isinstance(p, param.annotation): print(p,'!==',values[i].annotation) for k,v in kwargs.items(): if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation): print(k,v,'!===',params[k].annotation) return fn(*args, **kwargs) return wrapper @check def add(x, y:int=7) -> int: return x + y
print(add(x=20, y=10)) |
functools模块(2)
partial方法
偏函数, 把函数部分的参数固定下来, 相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回.
从partial生成得新函数,是对原函数的封装.
partial方法举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import functools
def add(x, y) -> int: return x + y
newadd = functools.partial(add, y=5) print(newadd(7)) print(newadd(7, y=6)) print(newadd(y=10, x=6))
import inspect
print(inspect.signature(newadd)) print('*' * 20)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import functools
def add(x, y, *args) -> int: print(args) return x + y
newadd = functools.partial(add, 1,3,6,5) print(newadd(7)) print(newadd(7, 10)) # print(newadd(9, 10, y=20, x=26)) # 报错. print(newadd())
import inspect
print(inspect.signature(newadd)) |
lru_cahce方法
@functools.lru_cache(maxsize=128, type=False)
least-recently-used装饰器. lru, 最近最少使用. cache缓存.
如果maxsize设置为None,则禁用LRU功能,并且缓存可无限制增长,当maxsize是二的幂时,LRU功能执行得最好.
如果typed设置为True,则不同类型的函数参数将单独缓存. 例:f(3)和f(3.0)将被视为具有不同结果的不同调用.
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import functools import time
@functools.lru_cache() def add(x, y, z=2): time.sleep(z) return(x+y)
print(add(4, 5)) print(add(4.0, 5)) print(add(4, 6)) print(add(4, 6, 3)) print(add(6, 4)) print(add(4, y=6)) print(add(x=4, y=6)) |
lru_cache装饰器:
通过一个字典缓存被装饰器函数的调用和返回值;
1 2 3 4 5 6 |
import functools
print(functools._make_key((4,6),{'z':3},False)) print(functools._make_key((4,6,3),{},False)) print(functools._make_key(tuple(),{'z':3,'x':4,'y':6},False)) print(functools._make_key(tuple(),{'z':3,'x':4,'y':6}, True)) |
斐波那契数列递归方法的改造:
1 2 3 4 5 6 7 8 |
import functools
@functools.lru_cache() # maxsize=None def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) print([fib(x) for x in range(35)]) |
lru_cache装饰器应用:
使用前提:
同样的函数参数一定得得到同样的结果;
函数执行时间很长,且要多次执行;
本质是函数调用的参数 -> 返回值.
缺点:
不支持缓存过期,key无法过期 失效.
不支持清除操作.
不支持分布式,是一个单机的缓存.
适用场景, 单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询.