Python进阶强化训练之装饰器使用技巧进阶
来源:互联网 发布:数据可视化分析方法 编辑:程序博客网 时间:2024/04/30 11:26
转载博客地址:https://ansheng.me/
内容源自刘硕老师的《Python进阶强化训练》
视频课程:http://coding.imooc.com/class/62.html
如何使用函数装饰器?
- 实际案例
某些时候我们想为多个函数,统一添加某种功能,比如记时统计、记录日志、缓存运算结果等等。
我们不想在每个函数内一一添加完全相同的代码,有什么好的解决方案呢?
解决方案
- 定义装饰奇函数,用它来生成一个在原函数基础添加了新功能的函数,替代原函数
如有如下两道题:
题目一
斐波那契数列又称黄金分割数列,指的是这样一个数列:1,1,2,3,5,8,13,21,….,这个数列从第三项开始,每一项都等于前两项之和,求数列第n项。
题目二
一个共有10个台阶的楼梯,从下面走到上面,一次只能迈1-3个台阶,并且不能后退,走完整个楼梯共有多少种方法?
脚本如下:
# 函数装饰器def memp(func): cache = {} def wrap(*args): if args not in cache: cache[args] = func(*args) return cache[args] return wrap# 第一题@mempdef fibonacci(n): if n <= 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2)print(fibonacci(50))# 第二题@mempdef climb(n, steps): count = 0 if n == 0: count = 1 elif n > 0: for step in steps: count += climb(n - step, steps) return countprint(climb(10, (1, 2, 3)))
输出结果:
C:\Python\Python35\python.exe E:/python-intensive-training/s11.py20365011074274Process finished with exit code 0
如何为被装饰的函数保存元数据?
- 实际案例
在函数对象张保存着一些函数的元数据,例如:
f.__name__
函数的名字f.__doc__
函数文档字符串f.__module__
函数所属模块名f.__dict__
属性字典f.__defaults__
默认参数元素我们在使用装饰器后,再使用上面的这些属性访问时,看到的是内部包裹函数的元数据,原来函数的元数据变丢失掉了,应该如何解决?
解决方案
- 使用标准库
functools
中的装饰器wraps
装饰内部包裹函数,可以指定将原来函数的某些属性更新到包裹函数上面
from functools import wrapsdef mydecoratot(func): @wraps(func) def wrapper(*args, **kwargs): """wrapper function""" print("In wrapper") func(*args, **kwargs) return wrapper@mydecoratotdef example(): """example function""" print('In example')print(example.__name__)print(example.__doc__)
输出结果:
C:\Python\Python35\python.exe E:/python-intensive-training/s12.pyexampleexample functionProcess finished with exit code 0
如何定义带参数的装饰器?
- 实际案例
实现一个装饰器,它用来检查被装饰函数的参数类型,装饰器可以通过指定函数参数的类型,调用时如果检测出类型不匹配则抛出异常,比如调用时可以写成如下:
@typeassert(str, int, int)def f(a, b, c): ......
或者
@typeassert(y=list)def g(x, y): ......
解决方案
- 提取函数签名:inspect.signature()
带参数的装饰器,也就是根据参数定制化一个装饰器,可以看成生产装饰器的工厂,美的调用typeassert
,返回一个特定的装饰器,然后用他去装饰其他函数。
from inspect import signaturedef typeassery(*ty_args, **ty_kwargs): def decorator(func): # 获取到函数参数和类型之前的关系 sig = signature(func) btypes = sig.bind_partial(*ty_args, **ty_kwargs).arguments def wrapper(*args, **kwargs): for name, obj in sig.bind(*args, **kwargs).arguments.items(): if name in btypes: if not isinstance(obj, btypes[name]): raise TypeError('"%s" must be "%s" ' % (name, btypes[name])) return func(*args, **kwargs) return wrapper return decorator@typeassery(int, str, list)def f(a, b, c): print(a, b, c)# 正确的f(1, 'abc', [1, 2, 3])# 错误的f(1, 2, [1, 2, 3])
执行结果
C:\Python\Python35\python.exe E:/python-intensive-training/s13.py1 abc [1, 2, 3]Traceback (most recent call last): File "E:/python-intensive-training/s13.py", line 28, in <module> f(1, 2, [1, 2, 3]) File "E:/python-intensive-training/s13.py", line 14, in wrapper raise TypeError('"%s" must be "%s" ' % (name, btypes[name]))TypeError: "b" must be "<class 'str'>" Process finished with exit code 1
如何实现属性可修改的函数装饰器?
- 实际案例
为分析程序内那些函数执行时间开销较大,我们定义一个带timeout参数的函数装饰器,装饰功能如下:
- 统计被装饰函数单词调用运行时间
- 时间大于参数timeout的,将此次函数调用记录到log日志中
- 运行时可修改timeout的值
解决方案
- 为包裹函数增加一个函数,用来修改闭包中使用的自由变量
在python3中使用nonlocal访问嵌套作用于中的变量引用
代码如下:
from functools import wrapsimport timeimport loggingfrom random import randintdef warn(timeout): # timeout = [timeout] # py2 def decorator(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) used = time.time() - start if used > timeout: # if used > timeout: # py2 msg = '"%s": "%s" > "%s"' % (func.__name__, used, timeout) # msg = '"%s": "%s" > "%s"' % (func.__name__, used, timeout[0]) # py2 logging.warn(msg) return res def setTimeout(k): nonlocal timeout timeout = k # timeout[0] = k # py2 wrapper.setTimeout = setTimeout return wrapper return decorator@warn(1.5)def test(): print('In Tst') while randint(0, 1): time.sleep(0.5)for _ in range(10): test()test.setTimeout(1)for _ in range(10): test()输出结果:
C:\Python\Python35\python.exe E:/python-intensive-training/s14.pyIn TstIn TstWARNING:root:"test": "2.503000259399414" > "1.5"In TstIn TstIn TstIn TstIn TstIn TstIn TstIn TstIn TstWARNING:root:"test": "1.0008063316345215" > "1"In TstIn TstIn TstWARNING:root:"test": "1.0009682178497314" > "1"In TstIn TstWARNING:root:"test": "1.5025172233581543" > "1"In TstIn TstIn TstIn TstProcess finished with exit code 0
如何在类中定义装饰器?
- 实际案例
实现一个能将函数调用信息记录到日志的装饰器:
- 把每次函数的调用时间,执行时间,调用次数写入日志
- 可以对被装饰函数分组,调用信息记录到不同日志
- 动态修改参数,比如日志格式
- 动态打开关闭日志输出功能
解决方案
- 为了让装饰器在使用上更加灵活,可以把类的实例方法作为装饰器,此时包裹函数中就可以持有实例对象,便于修改属性和扩展功能
代码如下:
import loggingfrom time import localtime, time, strftime, sleepfrom random import choiceclass CallingInfo: def __init__(self, name): log = logging.getLogger(name) log.setLevel(logging.INFO) fh = logging.FileHandler(name + '.log') # 日志保存的文件 log.addHandler(fh) log.info('Start'.center(50, '-')) self.log = log self.formattter = '%(func)s -> [%(time)s - %(used)s - %(ncalls)s]' def info(self, func): def wrapper(*args, **kwargs): wrapper.ncalls += 1 lt = localtime() start = time() res = func(*args, **kwargs) used = time() - start info = {} info['func'] = func.__name__ info['time'] = strftime('%x %x', lt) info['used'] = used info['ncalls'] = wrapper.ncalls msg = self.formattter % info self.log.info(msg) return res wrapper.ncalls = 0 return wrapper def SetFormatter(self, formatter): self.formattter = formatter def turnOm(self): self.log.setLevel(logging.INFO) def turnOff(self): self.log.setLevel(logging.WARN)cinfo1 = CallingInfo('mylog1')cinfo2 = CallingInfo('mylog2')# 设置日志指定格式# cinfo1.SetFormatter('%(func)s -> [%(time)s - %(ncalls)s]')# 关闭日志# cinfo2.turnOff()@cinfo1.infodef f(): print('in F')@cinfo1.infodef g(): print('in G')@cinfo2.infodef h(): print('in H')for _ in range(50): choice([f, g, h])() sleep(choice([0.5, 1, 1.5]))
转载博客地址:https://ansheng.me/
内容源自刘硕老师的《Python进阶强化训练》
视频课程:http://coding.imooc.com/class/62.html
0 0
- Python进阶强化训练之装饰器使用技巧进阶
- Python进阶强化训练之字符串处理技巧
- Python进阶强化训练之数据结构与算法进阶
- Python进阶强化训练之数据结构与算法进阶
- Python进阶强化训练之对象迭代与反迭代技巧
- Python进阶强化训练之文件I/O高效处理技巧
- Python进阶强化训练之csv|json|xml|excel高效解析与构建技巧
- Python进阶之装饰器
- Python进阶之装饰器
- Python进阶之装饰器
- Python进阶-装饰器
- Python进阶之装饰器@decorator
- python进阶强化-3
- python进阶强化-4
- python进阶强化-5
- python进阶强化-7
- PYTHON-进阶-装饰器小结
- python基础-装饰器进阶
- Swift - 动画效果的实现方法总结(附样例)
- androidstudio 和eclipse快捷键
- 变形--旋转 rotate()
- 关于EasyUI Datagrid 数据网格使用策略
- 字符串替换空格
- Python进阶强化训练之装饰器使用技巧进阶
- Android studio 理解jcenter和Maven Central
- js数据类型
- 变形--扭曲 skew()
- 第五章 head/全局对象
- 背景设置透明度字体不透明
- EDID 修改
- Argument list too long: recursive header expansion failed at
- QT5.5 百度翻译(post请求、MD5加密)