装饰器(Decorator)本质是函数,功能是为其他函数添加附加功能,定义一个装饰器需要满足以下两个原则:
- 不修改被修饰函数源代码(开放封闭原则)
- 不修改被修饰函数的调用方式
装饰器 = 高阶函数 + 函数嵌套 + 闭包
1. 高阶函数
高阶函数定义:
- 函数接收的参数是一个函数
- 函数的返回值是一个函数
满足以上任意一个条件就是高阶函数。
def calc():
print('calc 运行完毕~')
f = calc
f()
由于函数也是对象,因此函数也可以赋值给一个变量,调用这个变量就是调用函数。函数对象有一个 __name__
属性,用于查看函数的名字:
calc.__name__
f.__name__
'calc'
'calc'
需求:
在不修改 calc
源代码的前提下,为 calc
函数添加一个代码计时功能(计算 calc
运行时间)。
1.1 把函数当做参数传给另一个函数
使用高阶函数模拟装饰器实现calc
计时功能。
import time
def timmer(func):
start_time = time.time()
func()
stop_time = time.time()
print('函数 %s 运行时间为:%s' % (func.__name__, stop_time - start_time))
def calc():
time.sleep(2)
print('calc 运行完毕~')
timmer(calc)
calc 运行完毕~
calc 运行时间为:2.0011146068573
虽然为 calc
增加了计时功能,但 calc
原来的执行方式是 calc()
,而现在是调用高阶函数timmer(calc)
,改变了调用方式。
1.2 函数的返回值是函数
import time
def timmer(func): # func: calc
start_time = time.time()
func() # func(): calc()
stop_time = time.time()
print('函数 %s 运行时间为:%s' % (func.__name__, stop_time - start_time))
return func # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
def calc():
time.sleep(2)
print('calc 运行完毕~')
calc = timmer(calc) # calc = timmer(calc): calc = <function calc at 0x00000000052D6D90>
calc()
calc 运行完毕~
calc 的运行时间是:2.0001144409179688
calc 运行完毕~
没有改变 calc
的调用方式,但也没为其添加新功能。
1.3 总结
使用高阶函数实现装饰器功能 :
- 函数接收的参数是一个函数名
- **作用:**在不修改函数源代码的前提下,为函数添加新功能。
- **不足:**会改变函数的调用方式
- 函数返回值是一个函数名
- **作用:**不修改函数调用方式
- **不足:**不能添加新功能
2. 函数嵌套
函数中嵌套另一个函数
def father(name):
print('I am %s' % name)
def son():
print('My father is %s' % name)
def grandson():
print('My grandfather is %s' % name)
grandson()
son()
father('tom')
I am tom
My father is tom
My grandfather is tom
3. 闭包
闭包也是函数嵌套,如果在一个内部函数里调用外部作用域(不是全局作用域)的变量,那么这个内部函数就是闭包(closure)
def father(name):
print('I am %s' % name)
def son():
name = 'john'
def grandson():
print('My father is %s' % name)
grandson()
son()
father('tom')
I am tom
My father is john
内部函数 grandson 调用了它的外部函数 son 的变量 name='john'
,那么 grandson就是一个闭包。
4. 无参数装饰器
无参数装饰器 = 高阶函数 + 函数嵌套
4.1 基本框架
# 实现一个装饰器的基本框架
def timmer(func):
def wrapper():
func()
return wrapper
4.2 加上参数
def timmer(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
4.3 加上功能
import time
def timmer(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
4.4 加上返回值
import time
def timmer(func):
def wrapper(*args, **kargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print('函数 %s 运行时间:%s' % (func, stop_time - start_time))
return res
return wrapper
4.5 使用装饰器
def calc():
time.sleep(2)
print('calc 运行完毕~')
return 'calc 返回值'
calc = timmer(calc)
calc()
4.6 语法糖 @
@timmer # 相当于 calc = timmer(calc)
def calc():
time.sleep(2)
print('calc 运行完毕~')
calc()
4.7 示例
使用无参数装饰器,为 calc
添加计时功能(统计 calc
代码运行时间)
import timmer
def timmer(func): # func: calc
"""装饰器函数"""
def wrapper(*args, **kwargs): # args:<class 'tuple'>:('rose', 18, 'female')
start_time = time.time() # kwargs={}
res = func(*args, **kwargs) # res: 'calc 返回值'
stop_time = time.time()
print('函数 %s 运行时间:%s' % (func.__name__, stop_time - start_time))
return res # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
return wrapper
@timmer # @timmer: calc=timmer(calc) ——>calc = <function wrapper at 0x00000000052D6D90>
def calc(name, age, gender):
"""被修饰函数"""
time.sleep(2)
print('calc 运行完毕~')
print('名字:%s,年龄:%d,性别:%s' % (name, age, gender))
return 'calc 返回值'
s = calc('rose', 18, 'female') # 相当于执行 s = wrapper('rose', 18, 'female')
print(s)
calc 运行完毕
名字:rose,年龄:18,性别:female
函数 calc 运行时间:2.0001144409179688
calc 返回值
由于 timmer()
是一个装饰器函数,返回一个函数 wrapper
。所以 calc()
函数仍然存在,只是现在同名的 calc
变量指向了新的函数,于是调用 calc()
执行的是wrapper()
函数。
5. 有参数装饰器
有参数装饰器 = 高阶函数 + 函数嵌套 + 闭包
如果装饰器本山需要传入参数,就需要再编写一个 decorator
的高阶函数。比如给 calc
函数添加一个日志功能,能够打印日志,其基本框架如下:
def log(text):
def timmer(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
return timmer
@log('文本内容')
def calc():
pass
示例:
import timmer
def log(text): # text: '文本内容'
def timmer(func): # func: calc
"""装饰器函数"""
def wrapper(*args, **kwargs): # args:<class 'tuple'>:('rose', 18, 'female')
start_time = time.time() # kwargs={}
res = func(*args, **kwargs) # res: 'calc 返回值'
stop_time = time.time()
print('函数 %s 运行时间:%s' % (func.__name__, stop_time - start_time))
return res # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
return wrapper
return timmer
@log('文本内容') # 相当于 calc = log('自定义文本')(calc) ——> timmer(calc) ——> calc=wrapper
def calc(name, age, gender):
"""被修饰函数"""
time.sleep(2)
print('calc 运行完毕~')
print('名字:%s,年龄:%d,性别:%s' % (name, age, gender))
return 'calc 返回值'
s = calc('rose', 18, 'female') # 相当于执行 s = wrapper('rose', 18, 'female')
print(s)
与两层嵌套效果的decorator
相比,三层效果是这样的:
calc = log('自定义文本')(calc) # 即 calc = wrapper
首先执行 log('自定义文本')
,返回timmer
函数,再调用返回参数(timmer(calc)
),参数是 calc
,返回值是wrapper
函数,最后再调用wrapper()
。
6. 对象属性
函数也是对象,也有__name__
属性(返回函数名)。但经过装饰的calc
函数,它的__name__
从原来的calc
变成了wrapper
。
>>> calc.__name__
'wrapper'
有些需要依赖函数签名的代码因为__name__
改变,而出现某些错误,所以需要将原calc
的__name__
属性复制到wrapper()
函数中。
import functools
....
@functools.wraps(func)
def wrapper(*args, **kwargs)
...
7. 实例
实例 1
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
import time
import functools
def metric(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start_time = time.time()
res = fn(*args, **kwargs)
stop_time =time.time()
print('%s executed in %s ms' % (fn.__name__, stop_time - start_time))
return res
return wrapper
@metric # fast=metric(calc)
def fast(x, y):
time.sleep(0.0012)
return x + y
@metric
def slow(x, y, z):
time.sleep(0.1234)
return x * y * z
f = fast(11, 22)
s = slow(11, 22, 33)
print(f, s)
fast executed in 0.0019998550415039062 ms
slow executed in 0.1240072250366211 ms
33 7986
实例 2
实现一个购物网站基本功能,其功能如下:
- 提示用户输入用户名和密码
- 用户个人界面和购物车不需要登录(保持会话)
import time
import functools
# 模拟存储用户名、密码数据库
user_list=[
{'name':'alex','passwd':'123'},
{'name':'linhaifeng','passwd':'123'},
{'name':'wupeiqi','passwd':'123'},
{'name':'yuanhao','passwd':'123'},
]
current_dic = {'username': None, 'login': False} # 用于保存登录记录,None,False 为没有登录
def auth_func(func):
"""装饰器函数"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 仅供 home、shopping 调用(因为不需要再输入用户名和密码)
if current_dic['username'] and current_dic['login']:
res = func(*args, **kwargs)
return res
username = input('用户名:').strip()
passwd = input('密码:').strip()
# 遍历循环用户名、密码数据库,比对用户输入的用户名和密码
for user_dic in user_list:
if username == user_dic['name'] and passwd == user_dic['passwd']:
# 用户保持会话,即保持用户登录状态,赋值
current_dic['username'] = username
current_dic['login'] = True
res = func(*args, **kwargs)
return res
else:
print('用户名或密码错误')
return wrapper
@auth_func
def index():
"""主页(需要登录)"""
print('欢迎来到xxx主页!')
@auth_func
def home(name):
"""用户登录成功后的界面"""
print('欢迎回家 %s' % name)
@auth_func
def shopping_car(name):
"""购物车"""
print('%s购物车里有:[%s,%s,%s]' % (name, '游艇', '车子', '飞机'))
print('before-->',current_dic)
index()
print('after-->',current_dic)
home('rose')
shopping_car('rose')
before--> {'username': None, 'login': False}
用户名:alex
密码:123
欢迎来到京东主页!
after--> {'username': 'alex', 'login': True}
欢迎回家 rose
rose购物车里有:[游艇,车子,飞机]
带参数装饰器(需要认证类型):
import time
import functools
user_list=[
{'name':'alex','passwd':'123'},
{'name':'linhaifeng','passwd':'123'},
{'name':'wupeiqi','passwd':'123'},
{'name':'yuanhao','passwd':'123'},
]
current_dic={'username':None,'login':False}
def auth(auth_type='filedb'):
def auth_func(func):
@funtools.wraps(func)
def wrapper(*args,**kwargs):
print('认证类型是',auth_type)
if auth_type == 'filedb':
if current_dic['username'] and current_dic['login']:
res = func(*args, **kwargs)
return res
username=input('用户名:').strip()
passwd=input('密码:').strip()
for user_dic in user_list:
if username == user_dic['name'] and passwd == user_dic['passwd']:
current_dic['username']=username
current_dic['login']=True
res = func(*args, **kwargs)
return res
else:
print('用户名或者密码错误')
elif auth_type == 'ldap':
print('ldap 认证类型')
res = func(*args, **kwargs)
return res
else:
print('不知道什么认证方式')
res = func(*args, **kwargs)
return res
return wrapper
return auth_func
# 相当于 auth_func = auth(auth_type='filedb')(auth_func)
@auth(auth_type='filedb')
def index():
print('欢迎来到xxx主页')
@auth(auth_type='ldap')
def home(name):
print('欢迎回家%s' %name)
#
@auth(auth_type='sssssss')
def shopping_car(name):
print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','妹妹','娃娃'))
# print('before-->',current_dic)
# index()
# print('after--->',current_dic)
# home('产品经理')
shopping_car('产品经理')