开放封闭原则:
- 开放:对代码的拓展新功能开放。
- 封闭:对源码的修改以及调用方式的修改封闭。
装饰器:
-
在不改变原函数的代码以及其调用方式的前提下,为被装饰对象增加新功能。
-
完全遵循开放封闭原则,装饰器就是闭包的应用。
-
本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。
例如:给函数添加一个功能用来测试函数的执行效率,计算效率的方法就是用函数执行完毕后的时间减去函数执行前的时间:
import time
print(time.time()) # 显示此时此刻距离1970年1月1日0点0分0秒的秒数,也叫时间戳。
# 第一个版本
import time
def func1():
time.sleep(1) # 让程序停止1秒,模拟程序的执行耗时
print('Hello,word!')
start_time = time.time()
func1()
end_time = time.time()
print(end_time - start_time)
# 实现了测试效率的功能,但代码写死,只能测试这里的func1,如果有多处调用func1,那么要写大量重复代码。
# 第二个版本
import time
def func1():
time.sleep(1)
print('Hello,word!')
def timer(f):
start_time = time.time()
f()
end_time = time.time()
print(end_time - start_time)
timer(func1)
# 不用写重复代码,并且可以测试其他函数,但改变了函数的调用方式
# 第三个版本
import time
def func1():
time.sleep(1)
print('Hello,word!')
def timer(f):
def inner():
start_time = time.time()
f() # 装饰器的本质就是闭包,此时参数f是个自由变量。
end_time = time.time()
print(end_time - start_time)
return inner
func1 = timer(func1) # 传进函数内的func1并不会改变
func1() # 此时的func1()相当于inner()
# 将timer(func1)的返回值赋值给一个与被装饰函数同名的变量名,这样调用者看起来func1并没有变化。
# 在未改变原代码和调用方式的基础上,增加了功能,但多了一个赋值语句func1 = timer(func1)
# 若是调用其他函数则需要重复写代码,所以Python做了个优化:提出来语法糖的概念
语法糖。
# 第四个版本
import time
def timer(f): # timer 就是个装饰器
def inner():
start_time = time.time()
f()
end_time = time.time()
print(end_time - start_time)
return inner
@timer # 相当于 func1 = timer(func1) 写在函数之前
def func1():
time.sleep(1)
print('Hello,word!')
return 'in func1'
print(func1())
# 此时func1实际上为inner,原func1的返回值是返回给原func1,并非返回给inner,所以值为None
# 函数定义的正上方加语法糖才会调用timer
def func2():
time.sleep(1)
print("I'm fine")
return 'in func1'
func2()
# 此时装饰器基本实现,但无法接收到被装饰函数返回值,并且无法接收被装饰函数传参,还需要继续修改
# 版本五
import time
def timer(f):
def inner():
start_time = time.time()
r = f() # func1的调用实际在这一行,所以将func()的返回值赋值给r,将r返回给inner
end_time = time.time()
print(end_time - start_time)
return r
return inner
@timer
def func1():
time.sleep(0.5)
print('hello')
return 'in func1'
print(func1())
# 此时func1实际上为inner,inner的返回值为r,也就是原func1的返回值。
# 没有改变func1的调用方式,也没有改变func1的返回值,符合装饰器定义,接下来需要解决向func1传参。
# 版本六
import time
def timer(f):
def inner(*args,**kwargs): # 被装饰函数可能会接收各种参数,利用*的魔性用法可以接收所有参数
start_time = time.time()
r = f(*args,**args) # *放在函数调用能将接收的原组与字典拆分成位置实参和关键字实参。
end_time = time.time()
print(end_time - start_time)
return r
return inner
@timer
def func1(n,a='18'):
time.sleep(0.5)
print(f'hello,{n},今年{a})
return 'in func1'
print(func1('李白',n='33'))
# 此时装饰器timer已全部完成,当需要测试某函数的执行效率时,则在被装饰函数定义前加上语法糖语句即可
标准无参装饰器的格式:
def wrapper(f):
def inner(*args,**args):
'''被装饰函数执行前添加的功能'''
ret = f(*args,**args)
'''被装饰函数执行后添加的功能'''
return ret
return inner
语法糖
必须在装饰器定义之后,且是在被装饰函数的定义语句正上方使用语法糖,否则会报错。在语法糖和被装饰函数之间只能用#
号加注释,不能用三对单引号或双引号,会报语法错误。
格式:
def wrapper(func):
pass
@wrapper
# 注释1
# 注释2
def f():
pass
解释器在执行到语法糖@wrapper时会立即调用装饰器wrapper,然后将正下方的被装饰函数,当做参数传给wrapper,将返回值赋值给原函数名,语法糖固定就一个参数。
实例:
import time
def wrapper(func):
def inner(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print(stop - start)
return res
return inner
@wrapper
# 相当于 f1 = wrapper(f1)
def f1(x,y):
c = 0
for i in range(x):
for n in range(y):
c = i + n + c
return c
print(f1(1000,10000)) # 此时f1已经被狸猫换太子,其实是inner(1000,10000)
0.8780503273010254
54990000000
有参装饰器:
-
由于语法糖的关系,wrapper的参数不能改变,而inner的参数是接收被装饰函数的参数,也不能改变,那么若要向装饰器传参,则需要再最外层再定义一个函数,以此来接收传给装饰器的参数。
def outer(x,y,z): def wrapper(func): def inner(*args,**kwargs): '''被装饰函数执行前添加的操作''' ret = func(*args,**kwargs) '''被装饰函数执行后添加的操作''' return ret return inner return wrapper
此时则实现了一个有参装饰器,在被装饰函数定义语句上方使用:
@outer(x,y,z) # 装饰器outer括号内的参数可以按需求定义。 def func(): pass print(f1) # f1已经被换成了inner <function outer.<locals>.wrapper.<locals>.inner at 0x0000000002798C10>
@outer(x,y,z)会先调用outer(x,y,z)得到wrapper,等于@wrapper,也就是f1 = wrapper(f1),调用wrapper(f1)拿到返回值为inner,最终就是f1 = inner
wraps装饰器
为了将装饰后的函数伪装的与被装饰函数更相似,Python有个内置的装饰器wraps,能使调用者在查看装饰后函数的内置属性时,看似与原函数一致。
实例:
import time
from functools import wraps
def timmer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print('run time is:%s' % (stop - start))
return res
# wrapper.__name__=func.__name__
# wrapper.__doc__=func.__doc__
return wrapper
@timmer # index= timmer(index) # index=wrapper
def index():
"index函数"
time.sleep(1)
print('from index')
print(index) # 此时index实际上是wrapper内存地址。
print(index.__name__) # 查看函数名
help(index) # 查看函数注释信息。
结果:
<function index at 0x0000000002788DC0>
index
Help on function index in module __main__:
index()
index函数
如果将wraps注释掉:
<function timmer.<locals>.wrapper at 0x0000000002788DC0>
wrapper
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
# @wraps(func)
可以看到内存地址并没有变化,但名字都改变了。