闭包函数与装饰器
# 函数对象
# 介绍
即把函数内存地址当成对象进行传递,作为对象时,只需要写函数名,不可以加()。
def test():
print(123)
# 将test函数的内存地址给a,使得调用a同等于调用test
a = test
a()
2
3
4
5
# 应用案例
通过字典存储或调用函数对象。
def login():
...
def register():
...
# 存储函数对象
func_dic = {
'1': ('登录', login),
'2': ('注册', register
),
'0': ('退出', None)
}
for k in func_dic:
print(k, func_dic[k][0])
choice = input("请输入编号 >")
if choice in func_dic:
# 调用函数对象
func_dic[choice][1]()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 闭包函数
# 闭包函数
闭包函数结合使用了名称空间、作用域、函数嵌套、函数对象。
由于名字的查找关系是以函数定义阶段为准。所以闭包即是无论在哪里调取函数,使用的都是包里已经包好的名字与值。
# 闭
闭指的是该函数是内嵌函数,无法被直接访问到。
def f1():
def f2():
pass
2
3
# 包
包指的是该内嵌函数,包含对外n层函数作用域名字的引用,不包括全局作用域名字的引用。
def f1():
x = 1
def f2():
def f3():
print(x)
2
3
4
5
# 传递闭包的函数对象
我们将闭包函数作为对象传递给全局名称,此时闭包函数对象被全局名称引用。
由于闭包函数可以被全局名称访问到,所以闭包函数所包含的局部变量也不会被回收。
直到全局名称与闭包函数对象解除绑定关系,闭包函数所引用的变量空间才会被释放掉。
def f1():
x = 123
def f2():
print(x)
return f2
f = f1()
f()
>> 输出 123
2
3
4
5
6
7
8
# 装饰器介绍
装饰器是为其他函数添加额外功能的函数,会使用到*args
、**kwargs
、闭包函数。
装饰器的名称应当尽量描述装饰器的功能。
装饰指为其他事物添加额外的东西点缀,器指工具。
装饰器可以实现开放封闭原则,能在不修改函数源代码、调用方式、返回值的前提下,为其添加新的扩展功能。
# 开放封闭原则
开放,指的是对扩展功能是开放的。
封闭,指的是对修改源代码是封闭的。
想要在原函数之上增加一些额外扩展的新功能,不应该直接修改原函数。而是应该使用装饰器函数,在原有函数的基础上添加额外的功能。
因为原函数可能在很多地方都被调用,如果贸然修改则可能造成程序崩溃。
# 无参装饰器
例如在不修改源代码的情况下,添加计时功能。
# 方案一
直接修改原函数内代码。
缺点:违背开放封闭原则,可能造成其他调用该函数的地方出现问题。
import time
def say(content, times=1):
start = time.time()
time.sleep(1)
print(content * times) # 原代码被包裹
stop = time.time()
print(stop - start)
say("hello")
2
3
4
5
6
7
8
9
10
# 方案二
不修改原函数内代码,直接在原函数调用的前后添加代码。
缺点:需要反复使用时会代码冗余。
import time
def say(content, times=1):
print(content * times)
start = time.time()
time.sleep(1)
say("hello") # 函数调用被包裹
stop = time.time()
print(stop - start)
2
3
4
5
6
7
8
9
10
# 方案三
不修改原函数内代码,使用装饰函数包裹原函数和调用。
缺点:调用方式会被改变。
import time
def say(content, times=1):
print(content * times)
def timer(func, *args, **kwargs):
start = time.time()
time.sleep(1)
func(*args, **kwargs) # 调用传入的函数
stop = time.time()
print(stop - start)
timer(say, "hello")
2
3
4
5
6
7
8
9
10
11
12
13
# 方案四:无参装饰器
无参装饰器会对传入的函数对象进行装饰,装饰完后返回装饰好的函数对象。
调用装饰器函数将被装饰函数传入装饰器,同时使用被装饰函数的名称,来接收装饰器函数返回的函数对象。
名称相同同等于调用方式没有改变,又实现了装饰功能。
装饰器函数中定义了闭包函数,闭包函数会对传入的函数进行装饰。
闭包函数中有装饰代码和对被装饰函数的调用。同时会接收被装饰函数的返回值,作为闭包函数的返回值。
装饰器函数返回闭包函数的函数对象。
最后通过原函数名称来调用装饰后的函数即可。
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
time.sleep(1)
ret = func(*args, **kwargs) # 调用传入的函数
stop = time.time()
print(stop - start)
return ret # 返回传入的函数的返回值
return wrapper
def say(content, times=1):
print(content * times)
say = timer(say)
say("hello ", 2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 有参装饰器
如果装饰器函数需要传入参数,则需要再嵌套一层,以实现有参装饰器。
# 方案一
直接传递参数,两层嵌套就能实现。
缺点:调用方式会被改变。
import time
def timer(func, sleeptime=1):
def wrapper(*args, **kwargs):
start = time.time()
time.sleep(sleeptime) # 使用传入的参数
ret = func(*args, **kwargs) # 调用传入的函数
stop = time.time()
print(stop - start)
return ret # 返回传入的函数的返回值
return wrapper
def say(content, times=1):
print(content * times)
say = timer(say,2)
say("hello ", 2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方案二 - 有参装饰器
在无参装饰器的基础上,wrapper函数外面再加了一层函数嵌套,用于参数的传递,保证了调用方式不变。
最外层:用于函数所需要的参数的传递,并返回中间层的函数对象内存地址。
中间层:用于被装饰函数对象的传递,并返回最内层的函数对象内存地址。
最内层:用于装饰传入的函数对象,并返回函数对象的返回值。
import time
def timer(sleep=1):
def deco(func):
def wrapper(*args, **kwargs):
start = time.time()
time.sleep(sleep)
ret = func(*args, **kwargs) # 调用传入的函数
stop = time.time()
print(stop - start)
return ret # 返回传入的函数的返回值
return wrapper
return deco
def say(content, times=1):
print(content * times)
say = timer(3)(say)
say("hello ", 2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 装饰器语法糖
语法糖是Python提供给我们简化代码的语法,装饰器的语法糖用于简化装饰器的调用,使用方式是在被装饰器函数定义上方添加:@无参装饰器函数名
或 @有参装饰器函数名(参数)
@无参装饰器函数名
无参装饰器函数必须有且只能有一个参数,用于接收被装饰对象。
@有参装饰器函数名(参数)
@后跟函数调用可以应用于有参装饰器,我们可以用来传递参数,它会将该函数调用的返回结果作为无参装饰器函数,所以必须返回无参装饰器函数对象。
import time
def timer(sleep=1):
def deco(func):
def wrapper(*args, **kwargs):
start = time.time()
time.sleep(sleep)
ret = func(*args, **kwargs) # 调用传入的函数
stop = time.time()
print(stop - start)
return ret # 返回传入的函数的返回值
return wrapper
return deco
# 调用装饰器语法糖
@timer(3)
def say(content, times=1):
print(content * times)
say("hello ", 2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@timer(3)
会将实参3传入装饰器函数内并返回deco对象,变成@deco
。
@deco
会将下方的被装饰函数作为对象传入deco函数并返回wrapper对象。然后语法糖会将wrapper对象赋值给被装饰函数相同的名称say。
所以此处的
@timer(3)
同等于say = timer(3)(say)
# 叠加装饰器
一个函数可以被多个装饰器装饰。装饰时,会在原有装饰后对象的基础上再进行装饰。调用时,是先执行最外面的装饰代码,然后往内执行装饰代码。
# 同等于 run = timer3(timer2(timer1(run)))
@timmer3
@timmer2
@timmer1
def run(x):
...
2
3
4
5
6
# 伪装函数属性
# 手动伪装
wrapper.__name__ = func.__name__
伪装函数名属性。
wrapper.__doc__ = func.__doc__
伪装函数帮助信息。
添加在装饰器内,返回闭包函数对象之前添加(闭包函数外面)。
def timer(func):
def wrapper(*args, **kwargs):
... ...
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
2
3
4
5
6
# wraps函数
一个函数有很多属性,所以Python提供了用于复制所有函数属性的伪装装饰器。
from functools import wraps
导入伪装装饰器。
@wraps(函数)
会将括号内函数的属性,复制给下面一行被装饰的函数。我们在装饰器中闭包函数上面一行添加
@wraps(func)
即可。
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
... ...
return wrapper
2
3
4
5
6
7
没有要求其实不伪装函数属性也行,一般不影响使用。