装饰器的含义和作用:
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
注意:本文重点介绍装饰器如何使用,如需详细学习装饰器原理,推荐刘志军老师的装饰器讲解。
示例场景:
公司后端同学已经写好了两个接口供前端小伙伴使用,这两个接口没有做任何限制,可以直接调用。有一天老大告诉后端的同学:从现在开始,你们开发的每个接口对外提供调用的时候都要做身份验证,使用user:passwd的方式;验证不通过就不允许调用接口。
接口示例:
def get_item_price(name):
# todo: get data from your DB
# eg: data is stored in a dict now.
_dict = {'apple': 9, 'banana': 6}
return _dict[name]
def get_item_num(name):
# todo: get data from your DB
# eg: data is stored in a dict now.
_dict = {'apple': 300, 'banana': 256}
return _dict[name]
接口的功能很简单,根据商品名查询对应商品的价格和存货数量。
想要一劳永逸的给每个接口轻松实现user:passwd验证的话,装饰器是不二之选!下面是代码实现:
#装饰器
def verifier_decorator(func):
def wrapper(user,passwd,*args,**kwargs):
#todo: Read username and passwd from your DB and verify
#eg: passport is stored in a _dict now.
pass_dict = {'eli':'123','wang':'456'}
if (user,passwd) in pass_dict.items():
print('Success, Your passport has been verified!')
return func(*args,**kwargs)
else:
print('Failed, Your passport is not verified!')
return wrapper
# 使用@语法糖给函数调用装饰器
@verifier_decorator
def get_item_price(name):
# todo: get data from your DB
# eg: data is stored in a dict now.
_dict = {'apple': 9, 'banana': 6}
return _dict[name]
@verifier_decorator
def get_item_num(name):
# todo: get data from your DB
# eg: data is stored in a dict now.
_dict = {'apple': 300, 'banana': 256}
return _dict[name]
print(get_item_price('eli','13','apple'))
#Failed, Your passport is not verified!
#None
print(get_item_num('wang','456','apple'))
#Success, Your passport has been verified!
# 300
以上代码示例中,我在定义接口函数的时候只写了一个参数,而调用时传了三个参数却没有报错。
是因为前两个参数被装饰器中的闭包函数接收了,定义接口的时候是这么写的:
@verifier_decorator
def get_item_num(name):
这段代码等效于(把@那行注释,在调用接口前写上这行) :
get_item_price = verifier_decorator(get_item_price)
当实际调用的时候,此接口就非彼接口了,解释器真正执行的其实是装饰器中的闭包函数,所以调用的时候填多个参数也不会报错。可以理解为,装饰器取走了你定义的接口函数,又还了一个“一模一样”的函数给你。但这个过程中装饰器又干了点别的事情。【取走--原基础上添加其他代码--再还回】这一整个过程称为“装饰”。
当然,实际上它并不会还回一个“一模一样”的函数给你,装饰的过程中,原函数的元信息会被闭包函数替换掉:
def deco(func):
def wrapper(name):
'''I am wrapper'''
return func(name)
return wrapper
@deco
def print_name(name):
'''I am func'''
print(print_name.__doc__)
print(print_name.__name__)
print(name)
print_name('eli')
#I am wrapper
# wrapper
# eli
你如果明白了刚才说的“装饰”过程,这个地方就很容易理解了。即:真正执行的函数是装饰器中的闭包函数(wrapper)!
那应该如何保留原函数的元信息?
from functools import wraps #<<<---
def deco(func):
@wraps(func) #<<<---
def wrapper(name):
'''I am wrapper'''
return func(name)
return wrapper
@deco
def print_name(name):
'''I am func'''
print(print_name.__doc__)
print(print_name.__name__)
print(name)
print_name('eli')
#I am func
# print_name
# eli
Python标准库中的functools的wraps方法会在装饰器运行的时候将原函数的元信息一同传递给闭包函数,进而使得原函数对象的所有信息得以保留。
声明:本文章为个人对技术的理解与总结,不能保证毫无瑕疵,接收网友的斧正。