python中的装饰器有哪些-python 装饰器以及开发中常用的例子

有时候我们想为多个函数,同意添加某一种功能,比如及时统计,记录日志,缓存运算结果等等,而又不想改变函数代码

那就定义装饰器函数,用它来生成一个在原函数基础添加了新功能的函数,代替原函数

参考金角大王的博客

装饰器从无到有的过程

比如现在有三个已经实现功能的函数

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)

但是每次这样调用,如果很多模块要加这个功能,大家都要去调用一下,那太麻烦了

python中一切皆对象,可以用

shopping = login(shoping)

shopping()

这样的方式执行shopping()就等于执行login(shopping)

但是在前面赋值 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-实现斐波那契的几个方法

为什么要用装饰器实现斐波那契,因为实现过程中有很多重复的步骤,所以这样很浪费

36f84b20c962

image.png

def 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也会报错

36f84b20c962

ERROR

下面小练习:

如果有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 : 函数的名字

f.doc : 函数的文档字符串,对这个函数的一些描述

f.moudle : 函数所属的模块名

f.dict : 属性字典

f.defaults : 默认参数元祖

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]: (,)

In [28]: c = g.__closure__[0]

In [29]: c

Out[29]:

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

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 '

我们来看看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': ,

'b': ,

'c': })

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值