def add(x, y):
return x + y
def logger(fn):
def wrapper(*args, **kwargs):
print('调用前增强')
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
return ret
return wrapper
inner = logger(add)
x = inner(4, 5)
print(x)
#运行结果
调用前增强
调用后增强
9
可以进一步修改
def add(x, y):
return x + y
def logger(fn):
def wrapper(*args, **kwargs):
print('调用前增强')
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
return ret
return wrapper
add = logger(add)
x = add(4, 5)
print(x)
添加地址查询
便于理解
def add(x, y):
return x + y
def logger(fn):
def wrapper(*args, **kwargs):
print('调用前增强')
print('wrapper的地址:',id(wrapper))
ret = fn(*args, **kwargs) # 参数解构
print('fn此时的地址', id(fn))
print('调用后增强')
return ret
return wrapper
print('老add地址:', id(add))
add = logger(add)#先运行右边的,已经将老add地址通过传参,已经传给了logger函数的形参fn,返回的wrappr地址赋值给了add,覆盖了老add,相当于新建了一个新标识符
print('定义后的add地址:', id(add))
x = add(4, 5)#此时相当于wrappr(4,5)
print('定义后的add地址:', id(add))
print(x)
#运行结果
老add地址: 1772409699592
定义后的add地址: 1772409699864
调用前增强
wrapper的地址: 1772409699864
fn此时的地址 1772409699592
调用后增强
定义后的add地址: 1772409699864
9
用装饰器来改进
def logger(fn):
def wrapper(*args, **kwargs):
print('调用前增强')
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
return ret
return wrapper
@logger
def add(x, y):
return x + y
x = add(4, 5)
print(x)
logger 就是装饰器
add就是被包装函数
@logger就是装饰器语法
无参装饰器
上例的装饰器语法,称为无参装饰器
@符号后是一个函数
虽然是无参装饰器,但是@后的函数本质上是单参函数
上例的logger函数是一个高阶函数
单参就是将未包装的add传给装饰器logger
上例@logger相当于add=logger(add)
计时装饰器
def test_time(fn1):
def foo(x, y):
import datetime
star = datetime.datetime.now()
fn1(x, y)
end = datetime.datetime.now()
return end - star
return foo
def logger(fn):
def wrapper(*args, **kwargs):
print('调用前增强')
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
return ret
return wrapper
from time import sleep
@logger
@test_time
def add(x, y):
sleep(2)
return x + y
x = add(4, 5)
print(x)
带参装饰器
文档字符串
Python文档字符串Documentation Strings
在函数(类、模块)语句块的第一行,且习惯是多行的文本,所以多使用三引号
文档字符串也算是合法的一条语句
惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
可以使用特殊属性__doc__
访问这个文档
def add(x, y):
"""这是加法函数的文档"""
return x + y
print("{}'s doc = {}".format(add.__name__, add.__doc__))
print(help(add))
#运行结果
add's doc = 这是加法函数的文档
Help on function add in module __main__:
add(x, y)
这是加法函数的文档
None
或者在ipython中
def add1(x, y):
"""这是加法函数的文档"""
return x + y
add1
Signature: add1(x, y)
Docstring: 这是加法函数的文档
File: c:\users\administrator\desktop\学习笔记\python\代码\<ipython-input-6-757a8c9d3698>
Type: function
装饰器的文档字符串问题
import time, datetime
def logger(fn):
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
print('Function {} took {}s.'.format(fn.__name__, delta))
return ret
return wrapper
@logger # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
"""add's doc~~~"""
time.sleep(2)
return x + y
print(add.__name__, add.__doc__)
#结果
wrapper wrapper's doc~~~
显示的名字和文档却是替换后的
可以通过替换名字和文档来伪装称本函数
def copy_properties(src, dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
def logger(fn):
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
print('Function {} took {}s.'.format(fn.__name__, delta))
return ret
copy_properties(fn, wrapper)
return wrapper
可以添加复制函数
def copy_properties(src, dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
就可以达到目的
带参装饰器
可以将上例的函数改成装饰器
import time, datetime
#这个就相当与一个带单装饰器,带的参数是fn,也就是形参src,装饰的是logger中的wrapper,因为这里不需要实例化,所以没有带参数的第三层,而是直接返回了装饰的函数
def copy_properties(src):
def foo(dst):
"""这里是foo函数"""
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
#这里装饰完了,就将源函数弹出,继续下面的流程
return foo
def logger(fn):
@copy_properties(fn)
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
print('Function {} took {}s.'.format(fn.__name__, delta))
return ret
return wrapper
@logger # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
"""add's doc~~~"""
time.sleep(2)
return x + y
print(add.__name__, add.__doc__)
像@copy_properties(fn)
这种在装饰器后面跟着参数的装饰器称为带参装饰器
logger设定一个阈值,对执行时长超过阈值的记录一下
import time, datetime
def copy_properties(src):
def foo(dst):
"""这里是foo函数"""
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
return foo
def logger(duration):
def foo1(fn):
@copy_properties(fn)
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
print('slow') if delta > duration else print('fast')
return ret
return wrapper
return foo1
@logger(1)
def add(x, y):
"""add's doc~~~"""
time.sleep(2)
return x + y
print(add(2, 3))
像@copy_properties(fn)
这种在装饰器后面跟着参数的装饰器称为带参装饰器。logger设定一个阈值,对执行时长超过阈值的记录一下进一步提取记录功能,因为有可能输出到控制台,也可能写入日志,这个由一个函数提供。
import time, datetime
def copy_properties(src):
def foo(dst):
"""这里是foo函数"""
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
return foo
def logger(duration,out=lambda name,time:print('{}执行了{}s,太慢了!'.format(name,time))):#这里加入了一个函数,超过了阈值,就会提醒
#这里的out就是一个函数需要两个参数,一个是name,一个是time
def foo1(fn):
@copy_properties(fn)
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
# print('slow') if delta > duration else print('fast')
if delta>duration:
#当超时,就调用这个out函数
out(fn.__name__,delta)
return ret
return wrapper
return foo1
@logger(8)
def add(x, y):
"""add's doc~~~"""
time.sleep(2)
return x + y
print(add(2, 3))
属性更新
上例中copy_properties
是通用的功能,标准库中functools
已经提供了
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
需要引入函数update_wrapper
from functools import update_wrapper
类似
copy_properties
功能
wrapper
包装函数、被更新者,wrapped
被包装函数、数据源 元组
WRAPPER_ASSIGNMENTS
中是要被覆盖的属性,有模块名__module__
, 名称__name__
, 限定名__qualname__
, 文档__doc__
, 参数注解__annotations__
元组
WRAPPER_UPDATES
中是要被更新的属性,__dict__
属性字典 增加一个
__wrapped__
属性,保留着wrapped
函数
import time, datetime
from functools import update_wrapper
# def copy_properties(src):
# def foo(dst):
# """这里是foo函数"""
# dst.__name__ = src.__name__
# dst.__doc__ = src.__doc__
# return dst
#
# return foo
def logger(duration):
def foo1(fn):
# @copy_properties(fn)
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
return ret
update_wrapper(wrapper, fn)#这个函数调用相当于17行的装饰器功能
return wrapper
return foo1
@logger(8)
def add(x, y):
"""add's doc~~~"""
time.sleep(2)
return x + y
print(add(2, 3))
print(add.__name__, add.__doc__)
functools
模块提供了一个wraps
装饰器函数,本质上调用的是update_wrapper
,它就是一个属性复制函数
wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
需要导入函数
from functools import wraps
import time, datetime
from functools import update_wrapper,wraps
# def copy_properties(src):
# def foo(dst):
# """这里是foo函数"""
# dst.__name__ = src.__name__
# dst.__doc__ = src.__doc__
# return dst
#
# return foo
def logger(duration):
def foo1(fn):
# @copy_properties(fn)
@wraps(fn)#放在被装饰函数上面,用法跟上一行自写装饰器相同
def wrapper(*args, **kwargs):
"""wrapper's doc~~~"""
print('调用前增强')
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 参数解构
print('调用后增强')
delta = (datetime.datetime.now() - start).total_seconds()
return ret
# update_wrapper(wrapper, fn)
return wrapper
return foo1
@logger(8)
def add(x, y):
"""add's doc~~~"""
time.sleep(2)
return x + y
print(add(2, 3))
print(add.__name__, add.__doc__)
总结
@之后不是一个单独的标识符,是一个函数调用
函数调用的返回值又是一个函数,此函数是一个无参装饰器
带参装饰器,可以有任意个参数
@func()
@func(1)
@func(1, 2)
注解annotation
函数注解
def add(x:int, y:int) -> int:
"""
:param x:int
:param y:int
:return:int
"""
return x + y
print(add(1,2))
查询注解
print(add.__annotations__)
#运行结果
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
类型注解
i: int = 3
j:str = 'abc'
k: str = 300
print(i, j, k)
inspect模块
inspect模块可以获取Python中各种对象信息。
inspect.isfunction(add)
,是否是函数
inspect.ismethod(pathlib.Path().absolute)
,是否是类的方法,要绑定
inspect.isgenerator(add))
,是否是生成器对象
inspect.isgeneratorfunction(add))
,是否是生成器函数
inspect.isclass(add))
,是否是类
inspect.ismodule(inspect))
,是否是模块
inspect.isbuiltin(print))
,是否是内建对象
inspect.signature(callable, *, follow_wrapped=True)
获取可调用对象的签名
import inspect
def add(x: int, y: int) -> int:
"""
:param x:int
:param y:int
:return:int
"""
return x + y
print(add.__annotations__)
#结果
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
a = inspect.signature(add)
print(a)
#结果
(x:int, y:int) -> int#获取的签名
inspect.Parameter
4个属性
name,参数名,字符串
default,缺省值
annotation,类型注解
kind,类型
POSITIONAL_ONLY,只接受仅位置传参
POSITIONAL_OR_KEYWORD,可以接受关键字或者位置传参
VAR_POSITIONAL,可变位置参数,对应
*args
KEYWORD_ONLY,对应
*
或者*args
之后的出现的非可变关键字形参,只接受关键字传参 VAR_KEYWORD,可变关键字参数,对应
**kwargs
empty,特殊类,用来标记default属性或者annotation属性为空
inspect.signature(fn).parameters['x'].default
#取出对应形参的缺省值
#判断缺省值为空
a.parameters['x'].default==inspect.Parameter.empty
#对注解的判断
a.parameters["x"].annotation==int
获取参数属性
1、先获取签名
a=inspect.signature(fn)
print(a)
#结果
(x:int, y:int) -> int
2、通过签名获取属性
b=a.parameters
print(b)
#结果
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)])
#格式化成字典
c = dict(b)
print(c)
#结果
{'x': <Parameter "x:int">, 'y': <Parameter "y:int">}
#获得可用来比较的属性
print(c['x'].annotation)
#结果
<class 'int'>
写一个检查参数类型的装饰器
import inspect
from functools import wraps
def chack(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
# 先求位置参数的
m = list(args)
b = inspect.signature(fn).parameters
c = dict(b)
ve = list(b.values())
for i, n in enumerate(m):
if ve[i].annotation is not ve[i].empty and not isinstance(n, ve[i].annotation):#ve[i].empty表示默认值,这里为空
print(ve[i].name, n, '属性是no!的')
for k, v in kwargs.items():
if c[k].annotation is not c[k].empty and not isinstance(v, c[k].annotation):
print(k, v, '属性是no的')
ff = fn(*args, **kwargs)#最后需要返回结果
return ff
return wrapper
@chack
def add(x: int, y: int) -> int:
"""
:param x:int
:param y:int
:return:int
"""
return x + y
print(add('b', y='a'))
functools模块
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
用来更新函数属性 本身是个函数
wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
也是用来更新函数属性的,本身是个装饰器函数
reduce
functools.reduce(function, iterable[, initial])
就是减少的意思
初始值没有提供就在可迭代对象中取一个
求10的阶乘
print(reduce(lambda x, y: (x , y), range(1,11)))
#结果
(((((((((1, 2), 3), 4), 5), 6), 7), 8), 9), 10)
print(reduce(lambda x, y: x * y, range(1,11)))
#结果
3628800
partial
偏函数
把函数部分参数固定下来,相当于为部分的参数添加了固定的默认值,形成一个新的函数,并返回这个新函数
这个新函数是对原函数的封装
from functools import partial
import inspect
def add(x, y):
return x + y
newadd = partial(add, 11)
newadd1 = partial(add, x=11)
newadd2 = partial(add, y=11)
print(inspect.signature(newadd))
#结果
(y)
print(inspect.signature(newadd1))
#结果
(*, x=11, y)
print(inspect.signature(newadd2))
#结果
(x, *, y=11)
lru_cache
@functools.lru_cache(maxsize=128, typed=False)
lru即Least-recently-used,最近最少使用。cache缓存
如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂时,LRU功能执行得最好
如果typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用
利用缓存,计算递归的斐波那契数列
from functools import lru_cache
@lru_cache()
def fib(i):
if i==0:
return 0
elif i==1:
return 1
return fib(i-1)+fib(i-2)
print(fib(332))
运行结果
1082459262056433063877940200966638133809015267665311237542082678938909
标签:__,return,python,wrapper,add,print,装饰,def
From: https://www.cnblogs.com/guangdelw/p/16946070.html