Python 装饰器实用编程技巧总结,建议收藏学习

一、如何使用函数装饰器?

1.需求:

比如说,我们想为很多不同的函数添加相同的功能,比如说计时统计、记录日志、缓存运算结果等,但是我们又不想在每个函数中添加相同的代码

2.举个例子:

我们还是以斐波那契数的计算为例

示例代码:

def fib(n):
 if n <= 1:
 return 1
 return fib(n-1)+fib(n-2)
if __name__ == '__main__':
 print fib(50)

Python 装饰器实用编程技巧总结,建议收藏学习

有需要Python学习资料的小伙伴吗?小编整理【一套Python资料、源码和PDF】,感兴趣者可以关注小编后私信学习资料(是关注后私信哦)反正闲着也是闲着呢,不如学点东西啦

这一段代码想要跑出来非常的慢,因为我们在这个运算的过程中经历了非常多的重复运算,比如我们想计算50就要计算49,48 我们要计算49 就要计算 48,47 ,看到了吧,48 就出现了重复运算,那么这里面有着太多太多的重复运算,导致我们的计算非常的慢,并且非常的消耗 CPU

那么怎么办呢?

我们可以创造一个缓存,每次算到一个新的结果我们都放在这个缓存中,这样我们每次都判断缓存有没有我们想要的值就可以了,有的话直接拿过来用,没有再加入缓存,这样就能大大提高我们的运行效率,并且减轻了我们的CPU 的负担

示例代码:

def fib(n,cache = None):
 if cache is None:
 cache = {}
 if n in cache:
 return cache[n]
 if n <= 1:
 return 1
 cache[n] = fib(n-1,cache)+fib(n-2,cache)
 return cache[n]
if __name__ == '__main__':
 print fib(50)

结果:

20365011074

我们发现效率出现了质的飞跃,很快就算出了结果,但是换做别的函数我么又要添加这个缓存的代码了,这样就非常的烦,

那么怎么解决呢?

我们考虑创建一个包裹函数 wrap ,在这个函数内部我们实现我们的缓存代码,并且调用原函数,我们的函数装饰器就是为了生成这样的包裹函数的

示例代码:

def memo(func):
 cache = {}
 def wrap(*args):
 if args not in cache:
 cache[args] = func(*args)
 return cache[args]
 return wrap
def fib(n):
 if n <= 1:
 return 1
 return fib(n - 1) + fib(n - 2)
if __name__ == '__main__':
 fib = memo(fib)
 print fib(50)

结果:

20365011074

当然这样在函数中写是非常啰嗦的,我们python 给我们提供了一个语法糖,

示例代码:

def memo(func):
 cache = {}
 def wrap(*args):
 if args not in cache:
 cache[args] = func(*args)
 return cache[args]
 return wrap
@memo
def fib(n):
 if n <= 1:
 return 1
 return fib(n - 1) + fib(n - 2)
if __name__ == '__main__':
 print fib(50)

结果:

20365011074

二、如何为被装饰的函数保存元数据

1.概念:

在函数对象中保存着一些函数的元数据,例如:

f.__name__ 函数的名字

f.__doc__ 函数的文档字符串

f.__model__ 函数所属的模块名

f.__dict__ 属性字典

f.__defaults__ 默认参数元组

我们在使用装饰器以后,再使用上面这些属性访问的时候,看到的是包裹函数的元数据,而原始函数的元数据不见了,我们该如何解决

2.举个例子:

下面是原始函数的返回结果

示例代码:

def example():
 '''example function'''
 print "In example"
if __name__ == '__main__':
 print example.__name__
 print example.__doc__

结果:

example
example function

这下面是经过装饰器装饰以后的返回结果

示例代码:

def mydecorator(func):
 def wrap(*args,**kargs):
 '''wrap function'''
 print "In wrapper"
 func(*args,**kargs)
 return wrap
@mydecorator
def example():
 '''example function'''
 print "In example"
if __name__ == '__main__':
 print example.__name__
 print example.__doc__

结果:

wrap
wrap function

解决:

我们使用 functools 中的 wraps 装饰内部的包裹函数,可以定义将原函数的某些属性更新到包裹函数上面

示例代码:

from functools import wraps
def mydecorator(func):
 @wraps(func)
 def wrap(*args,**kargs):
 '''wrap function'''
 print "In wrapper"
 func(*args,**kargs)
 return wrap
@mydecorator
def example():
 '''example function'''
 print "In example"
if __name__ == '__main__':
 print example.__name__
 print example.__doc__

结果:

example
example function

三、如何自定义带参数的装饰器

比如说我们想实现一个装饰器来检查被装饰函数的参数类型,装饰器能定义函数的参数类型,如果函数调用的时参数类型不对就抛出异常

带参数的装饰器就是根据参数定制化一个装饰器,可以看成是生产装饰器的工厂,每次调用这个装饰器都能返回一个特定的装饰器,然后再用其修饰其它函数

示例代码:

from inspect import signature
def typeassert(*ty_args,**ty_kargs):
 def decorator(func):
 sig = signature(func)
 btypes = sig.bind_partial(*ty_args,**ty_kargs).arguments
 def wrapper(*args,**kargs):
 for name,obj in sig.bind(*args,**kargs).arguments.items():
 if name in btypes:
 if not isinstance(obj,btypes[name]):
 raise TypeError("%s must be %s" % (name,btypes[name]))
 return func(*args,**kargs)
 return wrapper
 return decorator
@typeassert(int,str,list)
def f(a,b,c):
 print (a,b,c)
if __name__ == '__main__':
 f(1,"abc",[1,2,3])
 f(1,2,[1,2,3])

结果:

1 abc [1, 2, 3]
TypeError: b must be <class 'str'>

四、如何实现属性可修改的装饰器

背景:

为了分析程序内哪些程序开销较大,我们可以定义一个带有timeout 参数的函数装饰器,他实现以下功能:

1.统计被装饰的函数的单次调用的时间

2.时间大于timeout 的将此次函数的调用记录记录在 log 日志文件中

3.运行时可以修改 timeout 的值

示例代码:

from functools import wraps
import time
import logging
from random import randint
def warn(timeout): 
 def decorator(func):
 @wraps(func)
 def wrapper(*args,**kargs):
 start = time.time()
 res = func(*args,**kargs)
 used = time.time() - start
 if used > timeout:
 msg = "%s : %s > %s" % (func.__name__,used,timeout)
 logging.warn(msg)
 return res
 return wrapper
 return decorator
@warn(1.5)
def test():
 print("In test")
 while randint(0,1):
 time.sleep(0.5)
for i in range(30):
 test()

我们可以在包裹中添加一个函数,然后用这个函数来修改闭包中的自由变量

Python3

示例代码:

from functools import wraps
import time
import logging
from random import randint
def warn(timeout):
 def decorator(func):
 @wraps(func)
 def wrapper(*args,**kargs):
 start = time.time()
 res = func(*args,**kargs)
 used = time.time() - start
 if used > timeout:
 msg = "%s : %s > %s" % (func.__name__,used,timeout)
 logging.warn(msg)
 return res
 def setTimeout(k):
 nonlocal timeout
 timeout = k
 wrapper.setTimeout = setTimeout
 return wrapper
 return decorator
@warn(1)
def test():
 print("In test")
 while randint(0,1):
 time.sleep(0.5)
for i in range(30):
 test()
test.setTimeout(1)
for i in range(30):
 test()

但是由于 python2 并不支持 nonlocal ,于是我们还要修改,使用列表将其修改成一个可变变量

示例代码:

from functools import wraps
import time
import logging
from random import randint
def warn(timeout):
 timeout = [timeout]
 def decorator(func):
 @wraps(func)
 def wrapper(*args,**kargs):
 start = time.time()
 res = func(*args,**kargs)
 used = time.time() - start
 if used > timeout[0]:
 msg = "%s : %s > %s" % (func.__name__,used,timeout[0])
 logging.warn(msg)
 return res
 def setTimeout(k):
 #nonlocal timeout
 timeout[0] = k
 wrapper.setTimeout = setTimeout
 return wrapper
 return decorator
@warn(1)
def test():
 print("In test")
 while randint(0,1):
 time.sleep(0.5)
for i in range(30):
 test()
test.setTimeout(1)
for i in range(30):
 test()

有需要Python学习资料的小伙伴吗?小编整理【一套Python资料、源码和PDF】,感兴趣者可以关注小编后私信学习资料(是关注后私信哦)反正闲着也是闲着呢,不如学点东西啦

相关推荐