有时候我们想为多个函数,同意添加某一种功能,比如及时统计,记录日志,缓存运算结果等等,而又不想改变函数代码
那就定义装饰器函数,用它来生成一个在原函数基础添加了新功能的函数,代替原函数
参考金角大王的博客
装饰器从无到有的过程
比如现在有三个已经实现功能的函数
def shoping():
print 'shoping'
def info():
print 'information'
def talking():
print 'talking'
然后然后想给每个模块加一个登陆验证功能
user_status = False # 先定义变量
def login():
_username = "ketchup" #假装这是DB里存的用户信息
_password = "123456" #假装这是DB里存的用户信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
else:
print("用户已登录,验证通过...")
一开始的想法是这样,在每个函数中添加函数
def shoping():
login()
print 'shoping'
def info():
login()
print 'info'
def talking():
login()
print 'talking'
但是,软件开发要遵循‘开放封闭的原则’,它规定已经实现的功能代码不允许被修改,但可以被扩展,
封闭:就是已经实现功能的代码块,尽量不在内部做修改
开放:对扩展开发
然后修改为:
user_status = False # 先定义变量
def login(func):
_username = "ketchup" #假装这是DB里存的用户信息
_password = "123456" #假装这是DB里存的用户信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func() #如果登陆成功,就调用传入的函数
def shoping():
print 'shoping'
def info():
print 'info'
def talking():
print 'talking'
login(shoping) #这样,先执行login函数,在login函数内部就会调用执行shoping函数
login(info)
login(talking)
但是每次这样调用,如果很多模块要加这个功能,大家都要去调用一下,那太麻烦了</br>
python中一切皆对象,可以用
shopping = login(shoping)
shopping()
这样的方式执行shopping()就等于执行login(shopping)</br>
但是在前面赋值 shopping = login(shoping)的时候,就已经调用login()函数了,执行了里面的func()函数
要解决这个问题,就要在shopping = login(shoping)这次调用的时候,不执行func函数,只是把一个函数名给了他,然后下面shoppin()函数执行的时候才会执行,
所以,就要在login函数里加一层闭包函数
def login(func):
def wrapper():
_username = "ketchup" #假装这是DB里存的用户信息
_password = "123456" #假装这是DB里存的用户信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func() #如果登陆成功,就调用传入的函数
return wrapper
这样的话,第一次shopping = login(shopping) 的时候,shopping 的值为wrapper
后面
def shoping():
print 'shoping'
的时候,才执行wrapper() ,才调用里面的func()
然后python对这种写法有一个语法糖 这种写法就等于在shopping函数前面加上@login
如果要在shopping里面传参数怎么办呢?
那就要在login里面把参数拿过来,然后传给func
def login(func):
def wrapper(*args,**kwargs):
_username = "ketchup" #假装这是DB里存的用户信息
_password = "123456" #假装这是DB里存的用户信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func(*args,**kwargs) #如果登陆成功,就调用传入的函数
return wrapper
@login
def shoping(num):
print 'shoping %d 个'%num
@login
def info():
print 'info'
@login
def talking():
print 'talking'
如果这时候要对login传参数呢,那就在多加一层对login参数的判断,比如,要判断是从qq还是weixin登陆的
def login(auth_type):
def auth(func):
def wrapper(*args,**kwargs):
if auth_type == 'qq':
_username = "ketchup" #假装这是DB里存的用户信息
_password = "123456" #假装这是DB里存的用户信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func(*args,**kwargs) #如果登陆成功,就调用传入的函数
else:
print('only support qq or weixin ')
return wrapper
return auth
下面以几个实际问题的例子深入理解装饰器
1-实现斐波那契的几个方法
为什么要用装饰器实现斐波那契,因为实现过程中有很多重复的步骤,所以这样很浪费
image.pngdef memo(func):
cache = {}
def wrap(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrap
@memo
def fib1(n):
if n<=1:
return 1
return fib1(n-1) + fib1(n-2)
def fib2(n,cache = None):
if cache is None:
cache = {}
if n in cache:
return cache[n]
if n<= 1:
return 1
cache[n] = fib2(n-1,cache) + fib2(n-2,cache)
return cache[n]
def fib3(n):
a,b = 1,1
while n >= 2:
a,b = b, a+b
n -= 1
return b
def fib4(n):
li = [1,1]
while n >=2:
li.append(li[-2]+li[-1])
n -= 1
return li[-1]
测试:
/ print(fib1(500))
print(fib2(500))
print(fib3(500))
print(fib4(500))
22559151616193633087251269503607207204601132491375819058863886641847462773868688340
5015987052796968498626
22559151616193633087251269503607207204601132491375819058863886641847462773868688340
5015987052796968498626
22559151616193633087251269503607207204601132491375819058863886641847462773868688340
5015987052796968498626
当到500 的时候,fib1已经报错了,RecursionError: maximum recursion depth exceeded in comparison
报错是因为每一级递归都需要调用函数, 会创建新的栈,
随着递归深度的增加, 创建的栈越来越多, 造成爆栈
当1000的时候,fib2 也报这个错误了
因为python 不支持尾递归,所以超过1000也会报错
ERROR
关于尾递归参照Python开启尾递归优化
下面小练习:
如果有n级台阶,每一次可以跨1-3级台阶,那么可以有多少种走法
@memo
def climb(n, steps):
count = 0
if n == 0:
count =1
elif n>0:
for step in steps:
count += climb(n - step, steps)
return count
#测试
print climb(200,(1,2,3))
52622583840983769603765180599790256716084480555530641
有时候我们想为多个函数,同意添加某一种功能,比如及时统计,记录日志,缓存运算结果等等
而又不想改变函数代码
定义装饰器函数,用它来生成一个在原函数基础添加了新功能的函数,代替原函数
2-为被装饰的函数保留原来的元数据
解决方法:
使用标准库functools中的装饰器wraps装饰内部包裹函数,可以定制原函>数的某些属性,更新到包裹函数上面
先看一下函数的元数据一般有哪些:
f.name : 函数的名字</br>
f.doc : 函数的文档字符串,对这个函数的一些描述</br>
f.moudle : 函数所属的模块名</br>
f.dict : 属性字典</br>
f.defaults : 默认参数元祖</br>
In [1]: def f():
...: '''f doc'''
...: print('ffff')
...:
In [11]: f.__doc__
Out[11]: 'f doc'
In [12]: f.__module__
Out[12]: '__main__'
In [13]: f.__defaults__
In [14]: def f(a, b=1, c=[]):
...: print a,b,c
...:
In [15]: f.__defaults__
Out[15]: (1, [])
In [17]: f.__defaults__[1].append('abc')
In [19]: f(5)
5 1 ['abc']
所以在默认参数里尽量不要使用可变对象
In [20]: f.__closure__
In [21]: def f():
...: a = 2
...: return lambda k:a**k
...:
In [22]: g = f()
In [27]: g.__closure__
Out[27]: (<cell at 0x11013af68: int object at 0x7ff0b05056e0>,)
In [28]: c = g.__closure__[0]
In [29]: c
Out[29]: <cell at 0x11013af68: int object at 0x7ff0b05056e0>
In [30]: c.cell_contents
Out[30]: 2
问题:
我们在使用装饰器装饰函数了之后,查看函数的元数据会显示是装饰器函数的元数据,而不是原函数的
def mydecorator(func):
def wrapper(*args,**kwargs):
'''wrapper function'''
print 'in wrapper'
return wrapper
@mydecorator
def example():
'''example function'''
print 'in example'
print example.__name__
print example.__doc__
运行结果:
wrapper</br>
wrapper function
解决1:
def mydecorator(func):
def wrapper(*args,**kwargs):
'''wrapper function'''
wrapper.__name__ = func.__name__
print 'in wrapper'
return wrapper
但是代码很不优雅
解决2:
from functools import update_wrapper
def mydecorator(func):
def wrapper(*args,**kwargs):
'''wrapper function'''
update_wrapper(wrapper, func, ('__name__', '__doc__'), ('__dic__'))
print 'in wrapper'
return wrapper
functools 里有两个默认参数,WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES 其实就对应着(‘module’, 'name', 'doc'), ('dic'),)
所以可以直接不用写,这两个是默认带的
解决3:
from functools import update_wrapper
def mydecorator(func):
def wrapper(*args,**kwargs):
'''wrapper function'''
update_wrapper(wrapper, func)
print 'in wrapper'
return wrapper
最后来说这个wraps,这个wraps 就是一个边界函数,他也是一个装饰器,是一个带参数的装饰器,可以直接用
使用标准库functools中的装饰器wraps装饰内部包裹函数,可以定制原函数的某些属性,更新到包裹函数上面
解决end:
from functools import update_wrapper,wraps
def mydecorator(func):
@wraps(func)
def wrapper(*args,**kwargs):
'''wrapper function'''
#update_wrapper(wrapper, func)
print 'in wrapper'
return wrapper
3-定义带参数的装饰器
实现一个装饰器,他用来检查呗装饰函数的参数类型,装饰器可以通过参数致命函数参数的类型,调用时如果检测出类型不匹配则抛出异常
@typeassert(str,int,int)
def f(a,b,c):
....
@typeassert(y= list)
def g(x,y):
...
解决方案, 带参数的装饰器也就是根据参数定制出一个装饰器可以看成生产装饰器的工厂,每次调用typeassert 返回一个特定的装饰器,然后用它去装饰其他函数
from inspect import signature
def typeassert(*ty_args, **ty_kargs):
def decorator(func):
# func -> a,b
# d = {'a': int, 'b': str}
sig = signature(func)
btypes = sig.bind_partial(*ty_args, **ty_kargs).arguments
def wrapper(*args, **kargs):
# arg in d, instance(arg, d[arg])
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)
测试
f(1,'666',[1,2,3])
f(1,2,[])
TypeError: 'b' must be '<class 'str'
我们来看看signature 是怎么用的
In [1]: from inspect import signature
In [2]: def f(a, b, c=1):pass
In [16]: c = sig.parameters
In [17]: c
Out[17]:
mappingproxy({'a': <Parameter "a">,
'b': <Parameter "b">,
'c': <Parameter "c=1">})
In [18]: c = sig.parameters['c']
In [20]: c.default
Out[20]: 1
如果想对a b c 简历一个类型的字典{‘a’:'int','b':'str','c':'list'}
In [23]: bargs = sig.bind(str,int,int)
In [24]: bargs.args
Out[24]: (str, int, int)
In [26]: bargs.arguments
Out[26]: OrderedDict([('a', str), ('b', int), ('c', int)])
In [29]: bargs.arguments['a']
Out[29]: str
但是如果sig.bind(str)
只传了一个参数,就会报错,如果想只穿一个参数的话,就用
sig.bind_partial(str)
例:写一个出错重试次数的装饰器,可以用来处理HTTP超时等
import requests
def retry(attempt):
def decorator(fuc):
def wrapper(*args, **kw):
att = 0
while att < 3:
try:
return func(*args, **kw)
except Exception as e:
att += 1
return wrapper
return decorator
#重试次数
@retry(attempt=3)
def get_response(url):
r = resquests.get('www.baidu.com')
return r.content
网友评论