Python装饰器
装饰器
-
需求
-
一个加法函数,想增加它的功能,能够输出被调用过以及调用的参数信息
-
def add(x, y): return x + y 增加信息输出功能 def add(x, y): print("call add, x + y") # 日志输出到控制台 return x + y
-
上面的加法函数是完成了需求,但是有 以下的缺点
- 打印是一个功能,这条语句和add函数耦合太高
- 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该在业务函数add中
-
-
下面代码做到了业务功能分离,但是fn函数调用传参是个问题
-
def add(x, y): return x + y def logger(fn): print('begin') # 增强的输出 x = fn(4, 5) # 参数写死了 print('end') # 增强的功能 return x print(logger(add))
-
-
解决了传参的问题,进一步改变
-
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))
-
-
柯里化
-
def add(x, y): return x + y def logger(fn): def wrapper(*args, **kwargs): print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper print(logger(add)(5, y=50))
-
-
装饰器语法糖
-
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(45, 40)) ---> begin end 85
-
-
装饰器(无参)
-
它是一个函数
-
函数作为它的形参,无参装饰器实际上就是一个单形参函数
-
返回值也是一个函数
-
可以使用@functionname方式,简化调用
注:此处装饰器的定义只是目前所学的总结,并不准确,只是方便理解
-
-
装饰器和高阶函数
- 装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
-
import datetime import time def logger(fn): def wrapper(*args, **kwargs): # before功能增强 print('args={}, kwargs={}'.format(args, kwargs)) start = datetime.datetime.now() # 计算时长 ret = fn(*args, **kwargs) # after功能增强 duration = datetime.datetime.now() - start print('function{}took{}s.'.format(fn.__name__,duration.total_seconds())) return ret return wrapper @logger # 相当于add = logger(add) def add(x, y): print('===call add=============') time.sleep(2) return x + y print(add(4, y=7)) ---> args=(4,), kwargs={'y': 7} ===call add============= functionaddtook2.000828s. 11
-
文档字符串
-
Python的文档
-
Python文档字符串Documentation Strings
-
在函数语句块的第一行,且习惯是多行文本,所以多使用三引号
-
文档字符串也算是合法的一条语句
-
惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
-
可以使用特殊属性____doc____访问这个文档
-
def add(x, y): """This is a function of addition""" return x + y print('name={}\ndoc={}'.format(add.__name__,add.__doc__)) print(help(add)) ---> name=add doc=This is a function of addition Help on function add in module __main__: add(x, y) This is a function of addition None
-
装饰器
-
副作用
-
def logger(fn): def wrapper(*args, **kwargs): 'i am wrapper' print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper @logger # add = logger(add) def add(x, y): """This is a function for add""" return x + y print('name={}, doc={}'.format(add.__name__, add.__doc__)) ---> name=wrapper, doc=i am wrapper
原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决?
-
-
提供一个函数,被封装函数属性 copy> 包装函数属性
-
def copy_properties(src, dst): # 可以改造成装饰器 dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ def logger(fn): def wrapper(*args, **kwargs): 'i am wrapper' print('begin') x = fn(*args, **kwargs) print('end') return x copy_properties(fn, wrapper) return wrapper @logger # add = logger(add) def add(x, y): """This is a function for add""" return x + y print('name={}, doc={}'.format(add.__name__,add.__doc__)) ---> name=add, doc=This is a function for add
-
-
通过copy_properties函数将被被包装函数的属性覆盖掉包装函数
-
凡是被装饰的函数都需要赋值这些属性,这个函数很通用
-
可以将复制属性的函数构建成装饰器函数,带参数装饰
-
提供一个函数,被封装函数属性 copy> 包装函数属性,改造成带参装饰器
-
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): 'I am wrapper' print('begin') x = fn(*args, **kwargs) print('end') return x return wrapper @logger # add = logger(add) def add(x, y): """This is a function for add""" return x + y print('name={}, doc={}'.format(add.__name__, add.__doc__)) ---> name=add, doc=This is a function for add
-
-
需求:获取函数的执行时长,对时长超过阈值的函数记录一下
-
def logger(duration): def _logger(fn): @copy_properties(fn) # wrapper = wrapper(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(3) return x + y print(add(5, 6)) ---> so fast 11
-
-
带参装饰器
- 它是一个函数
- 函数作为它的形参
- 返回值是一个不带参的装饰器函数
- 使用@functionname(参数列表)方式调用
- 可以看做在装饰器外层又加了一层函数,这个函数可以多参数
-
将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
-
def logger(duration, func=lambda name, delta:print('{}took{:.2f}s'.format(name, delta))): def _logger(fn): @copy_properties(fn) # wrapper = wrapper(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__, delta) return ret return wrapper return _logger
-
functools模块
-
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, update=WRAPPER_UPDATES)
-
类似copy_properties功能
-
wrapper包装函数、被更新者,wrapped被包装函数、数据源
-
元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
’module’,‘name’,‘qualname’,’_doc’,‘annotations’ 模块名、名称、限定名、文档、参数注解
-
元组WRAPPER_UPDATES中是要被更新的属性,____dict____属性字典
-
增加一个____wrapped____属性,保留着wrapped函数
-
import datetime, time, functools def logger(duration, func=lambda name, delta:print('{}took{:.2f}s'.format(name, delta))): def _logger(fn): 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 functools.update_wrapper(wrapper, fn) 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='\n') ---> 11 add <function add at 0x00000230B88369D8> {'__wrapped__': <function add at 0x00000230B88369D8>}
-
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS,update=WRAPPER_UPDATES)
-
类似copy_properties功能
-
wrapped被包装函数
-
元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
’module’,‘name’,‘qualname’,’_doc’,‘annotations’ 模块名、名称、限定名、文档、参数注解
-
元组WRAPPER_ASSIGNMENTS中是要被更新的属性,____dict____属性字典
-
增加一个____wrapped____属性,保留着wrapped函数
-
-
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() 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) # 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='\n') ---> 11 add <function add at 0x00000230B88F2C80> {'__wrapped__': <function add at 0x00000230B88F2C80>}
-
Python类型注解
函数定义的弊端
-
python是动态语言,变量随时可以被赋值,且能赋值为不同的类型
-
python不是静态编译型语言,变量类型是在运行期决定的
-
动态语言很灵活,但是这种特性也是弊端
-
def add(x, y): reutrn x + y print(add(4, 5)) # 9 print(add('hello', 'world')) # 'hello world' add(4, 'hello') # 报错 ------------------------------------------------- 难发现:由于不做任何类型检查,直到运行期间问题才显示出来,或者上线才能暴露出问题 难使用:函数的使用者看到的函数的时候,并不知道你的函数的设计,并不知道应该传什么类型的数据
-
-
如何解决这种动态语言定义的弊端呢?
-
增加文档Documentation String
-
这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档
-
函数定义更新,文档未必同步更新
-
def add(x, y): """ :param x: int :param y: int :return: int """ return x + y print(help(add))
-
-
-
如何解决这种动态语言定义的弊端?
-
函数注解
-
def add(x:int, y:int) -> int: """ :param x: int :param y: int :return: int """ return x + y print(help(add)) print(add(4, 5)) print(add('mag', 'edu'))
-
-
-
函数注解
-
python 3.5引入
-
对函数的参数进行类型注解
-
对函数的返回值进行类型注解
-
只对函数参数做一个辅助的说明,并不对函数参数进行类型检查,非强制
-
提供给第三方工具,做代码分析,发现隐藏的bug
-
函数注解的信息,保存在____annotations____属性中,字典类型
-
add.__annotations__ {'x':<class 'int'>, 'y':<class 'int'>, 'return':<class 'int'>}
-
-
变量注解
- Python 3.6引入。注意:它也是一种对变量的说明,非强制
-
-
业务应用
- 思路
- 函数参数的检查,最好是在函数外,尽量不要把检查代码写在函数中
- 函数应该作为参数,传入到检查函数中
- 检查函数拿到函数函数传入的实际参数,与形参声明对比
- ____annotations____属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块解决
- inspect模块
- 提供获取对象信息的函数,可以检查函数和类、类型检查
- 思路
inspect模块
- inspect.isfunction(add),是否是函数
- inspect.ismethod(pathlib.Path().absolute),是否是类的方法,要绑定
- inspect.isgenerator(add),是否是生成器对象
- inspect.isgeneratorfunction(add),是否是生成器函数
- inspect.isclass(add),是否是类
- inspect.ismodule(inspect),是否是模块
- inspect.isbuiltin(print),是否是内建对象
- 还有很多is函数,需要的时候查阅inspect模块帮助
signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)
import inspect
def add(x:int, y:int, *args, m:int, n:int, **kwargs) -> int:
return x + y + m + n
sig = inspect.signature(add) # 函数签名
print(sig, type(sig))
params = sig.parameters
print(type(params), params) # OrderedDict
print(sig.return_annotation)
for k,v in params.items():
print(k,v.annotation, type(v))
------------------------------------->
(x:int, y:int, *args, m:int, n:int, **kwargs) -> int <class 'inspect.Signature'>
<class 'mappingproxy'> OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('m', <Parameter "m:int">), ('n', <Parameter "n:int">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
x <class 'int'> <class 'inspect.Parameter'>
y <class 'int'> <class 'inspect.Parameter'>
args <class 'inspect._empty'> <class 'inspect.Parameter'>
m <class 'int'> <class 'inspect.Parameter'>
n <class 'int'> <class 'inspect.Parameter'>
kwargs <class 'inspect._empty'> <class 'inspect.Parameter'>
-
Parameter对象
-
保存在元组中,是只读的
-
name,参数的名字
-
annotation,参数的注解,可能没有定义
-
default,参数的缺省值,可能没有定义
-
empty,特殊的类,用来标记default属性或者注释annotation属性的空值
-
kind,实参如何绑定到形参,就是形参的类型
- POSITIONAL_ONLY,值必须是位置参数提供
- POSITIONAL_OR_KEYWORLD,值可以作为关键字或者位置参数提供
- VAR_POSITIONAL,可变位置参数,对应*args
- KEYWORD_ONLY,keyword_only参数,对应*或者**args之后的出现的非可变关键字参数
- VAR_KEYWORD,可变关键字参数,对应**kwargs
-
举例
-
import inspect def add(x, y:int=7, *args, m:int, n=10, **kwargs) -> int: return x + y + m + n sig = inspect.signature(add) # 函数签名 print(sig, type(sig)) params = sig.parameters print(type(params), params) # OrderedDict print(sig.return_annotation) for i,(k,v) in enumerate(params.items(), 1): print(i, k,v.name, v.kind, v.default, v.annotation, sep='\t') ----------------------------------------------- (x, y:int=7, *args, m:int, n=10, **kwargs) -> int <class 'inspect.Signature'> <class 'mappingproxy'> OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">), ('args', <Parameter "*args">), ('m', <Parameter "m:int">), ('n', <Parameter "n=10">), ('kwargs', <Parameter "**kwargs">)]) <class 'int'> 1 x x POSITIONAL_OR_KEYWORD <class 'inspect._empty'> <class 'inspect._empty'> 2 y y POSITIONAL_OR_KEYWORD 7 <class 'int'> 3 args args VAR_POSITIONAL <class 'inspect._empty'> <class 'inspect._empty'> 4 m m KEYWORD_ONLY <class 'inspect._empty'> <class 'int'> 5 n n KEYWORD_ONLY 10 <class 'inspect._empty'> 6 kwargs kwargs VAR_KEYWORD <class 'inspect._empty'> <class 'inspect._empty'>
-
-
-
有函数如下
-
def add(x, y:int=7) -> int: return x + y ------------------------------------------ 请检查用户输入是否符合参数注解的要求? 思路: 调用时,用户传入实参,才能判断用户输入的实参是否符合要求 调用时,用户感觉上还是在调用add函数 对用户输入的数据和声明的类型进行对比,如果不符合,提示用户 ------------------------------------------ import inspect def check(fn): def wrapper(*args, **kwargs): sig = inspect.signature(fn) # 函数签名 params = sig.parameters values = list(params.values()) print(sig, params, values) for i,p in enumerate(args): if isinstance(p, values[i].annotation): print(p, values[i].annotation) for k,v in kwargs.items(): if isinstance(v, params[k].annotation): print(v, params[k].annotation) return fn(*args, **kwargs) return wrapper def add(x, y:int=7) -> int: return x + y ---------------------------------------------------------- check(add)(20, 10) (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">] 10 <class 'int'> 30 ---------------------------------------------------------- check(add)(20, y=10) (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">] 10 <class 'int'> 30 ---------------------------------------------------------- check(add)(y=10, x=20) (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">] 10 <class 'int'> 30
-
import inspect def check(fn): def wrapper(*args, **kwargs): sig = inspect.signature(fn) # 函数签名 params = sig.parameters values = list(params.values()) print(sig, params, values) for i,p in enumerate(args): value = values[i] if value.annotation is not value.empty and not isinstance(p, values[i].annotation): print(p, 'is not',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(v, 'is not', params[k].annotation) return fn(*args, **kwargs) return wrapper @check def add(x, y:int=7) -> int: return x + y ---------------------------------------------------- add(20, 10) (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">] 30 ---------------------------------------------------- add(20, y=10) (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">] 30 ---------------------------------------------------- add(y=10, x=20) (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">] 30
-
Python之functools
functools模块
-
reduce方法
-
reduce方法,顾名思义就是减少
-
reduce(function,sequence[,initial]) -> value
-
可迭代对象不能为空;初始值没提供就在可迭代对象中取一个元素
-
from functools import reduce nums = [6, 9, 4, 2, 4, 10, 5, 9, 6, 9] print(nums) print(sum(nums)) print(reduce(lambda val, x:val + x, nums)) ---> [6, 9, 4, 2, 4, 10, 5, 9, 6, 9] 64 64
-
-
-
partial方法
-
偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回
-
从partial生成的新函数,是对原函数的封装
-
举例
-
import functools def add(x, y) -> int: return x + y newadd = functools.partial(add, y=5) print(newadd(7)) # 12 print(newadd(7, y=6)) # 13 print(newadd(y=10, x=6)) # 16 import inspect print(inspect.signature(newadd)) # (x, *, y=5) -> int
-
# partial方法举例 import functools def add(x, y, *args) -> int: print(args) return x + y newadd = functools.partial(add, 1, 3, 6, 5) print(newadd(7)) # (6, 5, 7) 4 print(newadd(7, 10)) # (6, 5, 7, 10) 4 print(newadd(9, 10, y=20, x=26)) # 报错 print(newadd()) # (6, 5) 4 import inspect print(inspect.signature(newadd)) # (*args) -> int
-
-
-
partial函数本质
-
def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): # 包装函数 newkeywords = kewwords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) newfunc.func = func # 保留原函数 newfunc.args = args # 保留原函数的位置参数 newfunc.keywords = keywords # 保留原函数的关键字参数 return newfunc def add(x, y): return x + y foo = partial(add, 4) foo(5) ---> 9
-
-
partial函数—分析functools.wraps的实现
-
@functools.lre_cache(maxsize=128, typed=False)
-
Least-recently-used装饰器。lru,最近最少使用。cache缓存
-
如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂时,LRU功能执行的最好
-
如果typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用
-
import functools import time @functools.lru_cache() def add(x, y, z=3): time.sleep(z) return x + y add(4, 5) # 9 add(4.0, 5) # 9.0 add(4, 6) # 10 add(4, 6, 3) # 10 add(6, 4) # 10 add(4, y=6) # 10 add(x=4, y=6) # 10 add(y=6, x=4) # 10
-
-
-
lru_cache装饰器
-
通过一个字典缓存被装饰函数的调用和返回值
functools._make_key((4, 6),{'z':3},False) -> [4, 6, <object at 0x1bb715ab0b0>, 'z', 3] ----------------------------------------------- functools._make_key((4, 6, 3), {}, False) -> [4, 6, 3] ----------------------------------------------- functools._make_key(tuple(), {'z':3, 'x':4, 'y':6}, False) -> [<object at 0x1bb715ab0b0>, 'z', 3, 'x', 4, 'y', 6] ----------------------------------------------- functools._make_key((), {'z':3, 'x':4, 'y':6}, True) -> [<object at 0x1bb715ab0b0>, 'z', 3, 'x', 4, 'y', 6, int, int, int]
-
斐波那契数列递归方法的改造
-
import functools @functools.lru_cache() # maxsize = None def fib(n): return 1 if n < 3 else fib(n-1) + fib(n-2) print([fib(i + 1) for i in range(1, 36)]) ---> [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352]
-
-
lru_cache装饰器应用
- 使用前提
- 同样的函数参数一定得到同样的结果
- 函数执行时间很长,且要多次执行
- 本质时函数调用的参数 => 返回值
- 缺点
- 不支持缓存过期,key无法过期、失效
- 不支持清除操作
- 不支持分布式,是一个单机的缓存
- 使用场景:单机上需要空间换时间的地方,可以使用缓存来将计算变成快速的查询
- 使用前提
-