装饰器 == 高阶函数+函数嵌套+闭包
装饰器 == 高阶函数+函数嵌套+闭包
装饰器的基本概念
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,
有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是在不改变函数调用方式的前提下为已经存在的函数对象添加额外的功能。
def foo(): print('i am foo')
def foo(): print('i am foo') logging.info("foo is running")
def use_logging(func): logging.warn("%s is running" % func.__name__) func() def bar(): print('i am bar') use_logging(bar)
逻辑上不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。
而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar(),但是现在不得不改成use_logging(bar)。
那么有没有更好的方式的呢?当然有,答案就是装饰器。
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args, **kwargs) return wrapper def bar(): print('i am bar') bar = use_logging(bar) bar()
函数use_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被use_logging装饰了
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作,即省略了bar = use_logging(bar)
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args) return wrapper @use_logging #==>foo = use_logging(foo) def foo(): print("i am foo") @use_logging #==>bar = use_logging(bar) def bar(): print("i am bar") foo() bar()
装饰器的本质是:高阶函数+函数嵌套+闭包
高阶函数定义:
1.函数接收的参数是一个函数名
2.函数的返回值是一个函数名
满足上述条件任意一个,都可称之为高阶函数
高阶函数范例
def foo(): print('我的函数名作为参数传给高阶函数') def gao_jie1(func): print('我就是高阶函数1,我接收的参数名是%s' %func) func() def gao_jie2(func): print('我就是高阶函数2,我的返回值是%s' %func) return func gao_jie1(foo) gao_jie2(foo)
把函数当做参数传给高阶函数
#高阶函数应用1:把函数当做参数传给高阶函数 import time def foo(): print('from the foo') def timmer(func): start_time=time.time() func() stop_time=time.time() print('函数%s 运行时间是%s' %(func,stop_time-start_time)) timmer(foo) #总结:我们确实为函数foo增加了foo运行时间的功能,但是foo原来的执行方式是foo(),
现在我们需要调用高阶函数timmer(foo),改变了函数的调用方式
函数返回值是函数名
#高阶函数应用2:把函数名当做参数传给高阶函数,高阶函数直接返回函数名 import time def foo(): print('from the foo') def timmer(func): start_time=time.time() return func stop_time=time.time() print('函数%s 运行时间是%s' %(func,stop_time-start_time)) foo=timmer(foo) foo() #总结:我们确实没有改变foo的调用方式,但是我们也没有为foo增加任何新功能
高阶函数总结
1.函数接收的参数是一个函数名
作用:在不修改函数源代码的前提下,为函数添加新功能,
不足:会改变函数的调用方式
2.函数的返回值是一个函数名
作用:不修改函数的调用方式
不足:不能添加新功能
函数嵌套
def father(name): print('from father %s' %name) def son(): print('from son') def grandson(): print('from grandson') grandson() son() father('林海峰')
闭包
''' 闭包:在一个作用域里放入定义变量,相当于打了一个包
本例中son函数使用了father函数的变量name ''' def father(name): def son(): # name='alex' print('我爸爸是 [%s]' %name) def grandson(): # name='wupeiqi' print('我爷爷是 [%s]' %name) grandson() son() father('林海峰')
无参装饰器和有参装饰器
装饰器基本框架
def timer(func): def wrapper(): func() return wrapper
加上参数
def timer(func): def wrapper(*args,**kwargs): func(*args,**kwargs) return wrapper
加上功能
#计算函数func执行时间 import time def timer(func): def wrapper(*args,**kwargs): start_time=time.time() func(*args,**kwargs) stop_time=time.time() print('函数[%s],运行时间是[%s]' %(func,stop_time-start_time)) return wrapper
加上返回值
import time def timer(func): def wrapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time=time.time() print('函数[%s],运行时间是[%s]' %(func,stop_time-start_time)) return res return wrapper
使用装饰器
def cal(array): res=0 for i in array: res+=i return res cal=timer(cal) cal(range(10))
使用语法糖 @
@timer #@timer就等同于cal=timer(cal) def cal(array): res=0 for i in array: res+=i return res cal(range(10))
装饰器案例
无参装饰器
user_list=[ {'name':'alex','passwd':'123'}, {'name':'linhaifeng','passwd':'123'}, {'name':'wupeiqi','passwd':'123'}, {'name':'yuanhao','passwd':'123'}, ] current_user={'username':None,'login':False} def auth_deco(func): def wrapper(*args,**kwargs): if current_user['username'] and current_user['login']: res=func(*args,**kwargs) return res username=input('用户名: ').strip() passwd=input('密码: ').strip() for index,user_dic in enumerate(user_list): if username == user_dic['name'] and passwd == user_dic['passwd']: current_user['username']=username current_user['login']=True res=func(*args,**kwargs) return res break else: print('用户名或者密码错误,重新登录') return wrapper @auth_deco def index(): print('欢迎来到主页面') @auth_deco def home(): print('这里是你家') def shopping_car(): print('查看购物车啊亲') def order(): print('查看订单啊亲') print(user_list) # index() print(user_list) home()
有参装饰器
user_list=[ {'name':'alex','passwd':'123'}, {'name':'linhaifeng','passwd':'123'}, {'name':'wupeiqi','passwd':'123'}, {'name':'yuanhao','passwd':'123'}, ] current_user={'username':None,'login':False} def auth(auth_type='file'): def auth_deco(func): def wrapper(*args,**kwargs): if auth_type == 'file': if current_user['username'] and current_user['login']: res=func(*args,**kwargs) return res username=input('用户名: ').strip() passwd=input('密码: ').strip() for index,user_dic in enumerate(user_list): if username == user_dic['name'] and passwd == user_dic['passwd']: current_user['username']=username current_user['login']=True res=func(*args,**kwargs) return res break else: print('用户名或者密码错误,重新登录') elif auth_type == 'ldap': print('巴拉巴拉小魔仙') res=func(*args,**kwargs) return res return wrapper return auth_deco #auth(auth_type='file')就是在运行一个函数,然后返回auth_deco,所以@auth(auth_type='file') #就相当于@auth_deco,只不过现在,我们的auth_deco作为一个闭包的应用,外层的包auth给它留了一个auth_type='file'参数 @auth(auth_type='ldap') def index(): print('欢迎来到主页面') @auth(auth_type='ldap') def home(): print('这里是你家') def shopping_car(): print('查看购物车啊亲') def order(): print('查看订单啊亲') # print(user_list) index() # print(user_list) home()
import logging def use_logging(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%s is running" % func.__name__) return func(*args) return wrapper return decorator @use_logging(level="warn") #foo=use_logging(foo) => foo=decorator(foo) =>wrapper def foo(name='foo'): print("i am %s" % name) foo()
类装饰器
再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。
使用类装饰器还可以依靠类内部的 __call__ 方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending') @Foo def bar(): print ('bar') bar()
functools.wraps
#装饰器 def logged(func): def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging #函数 @logged def f(x): """does some math""" return x + x * x #语法糖@logged等价于 f = logged(f) def f(x): """does some math""" return x + x * x f = logged(f)
不难发现,函数f被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。
这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,
它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print(f.__name__) # prints 'f' print(f.__doc__) # prints 'does some math'
内置装饰器
@staticmathod、@classmethod、@property
@a @b @c def f (): xxoo #等价于 f = a(b(c(f)))
参考:
https://www.cnblogs.com/linhaifeng/articles/6140395.html