Life consists not in holding good cards but in playing those you hold well.
一、闭包函数
装饰器的本质是闭包,我们先了解下闭包函数:
- 1.在函数内部定义的函数
- 2.引用了外部变量(但非全局变量)
也就是说,引用了外部变量(非全局变量)的这个嵌套内部函数被称为闭包函数。
判断是否是闭包函数
__closure__
属性,用来判断该函数是否是闭包函数。如果是,返回 cell;否则,返回 None。
代码示例:
global_param = 100 # 全局变量
def outFunc(x):
temp0 = 1 + x
# 引用外部变量(非全局变量),inFunc1是闭包函数
def inFunc1():
temp1 = 2
return temp0 + temp1
# 引用外部的"全局变量",inFunc2不是闭包函数
def inFunc2():
temp2 = 3
return temp2 + global_param
# 不引用外部的变量,inFunc3不是闭包函数
def inFunc3():
temp3 = 4
return temp3
# 函数名.__closure__属性,用来判断函数是否是闭包
print("inFunc1是否是闭包:", inFunc1.__closure__) # 是闭包,返回cell
print("inFunc2是否是闭包:", inFunc2.__closure__) # 不是闭包,返回None
print("inFunc3是否是闭包:", inFunc3.__closure__) # 不是闭包,返回None
return inFunc1() + inFunc2() + inFunc3()
print(outFunc(5))
print("outFunc是否是闭包:", outFunc.__closure__)
运行结果:
inFunc1是否是闭包: (<cell at 0x10617c690: int object at 0x105a06520>,)
inFunc2是否是闭包: None
inFunc3是否是闭包: None
115
outFunc是否是闭包: None
分析:
- inFunc1 引用了外部变量(且非全局变量)temp0,是闭包函数。
- inFunc2 引用了外部变量(但却是全局变量)global_param,不是闭包函数。
- inFunc3 没有引用外部变量,不是闭包函数。
- outFunc 不是内嵌函数,不是闭包函数。
通过闭包函数改变外部变量
def outer_func():
loc_list = []
def inner_func(name):
loc_list.append(len(loc_list) + 1)
print('name=%s, loc_list=%s' % (name, loc_list))
return inner_func
# 获取闭包函数
wrapper_func = outer_func()
print(wrapper_func.__name__, wrapper_func.__closure__)
# 通过多次调用改变外部变量
wrapper_func("func1")
wrapper_func("func2")
wrapper_func("func3")
print("------")
outer_func()('func1')
outer_func()('func2')
outer_func()('func3')
运行结果:
inner_func (<cell at 0x1076f0350: list object at 0x1070f61e0>,)
name=func1, loc_list=[1]
name=func2, loc_list=[1, 2]
name=func3, loc_list=[1, 2, 3]
------
name=func1, loc_list=[1]
name=func2, loc_list=[1]
name=func3, loc_list=[1]
闭包陷阱
注意,外部变量的取值,是调用闭包函数的那一时刻所对应的值,这里容易混淆。
通过下面两个示例,有助于我们进一步理解闭包陷阱。
1)是闭包函数:
def my_func(*args):
fs = []
# i是外部变量,执行完for循环后i=2
for i in range(3):
# func引用外部的i变量,是闭包函数
def func():
return i * i
print(id(func), func.__closure__)
fs.append(func)
# 到这里,i=2
return fs
# 执行完后,返回三个函数,并且外部变量i=2
fs1, fs2, fs3 = my_func(3)
# i是调用函数这一时刻的外部变量i值:由于i=2,所以i*i=4
print(fs1()) # 4
print(fs2()) # 4
print(fs3()) # 4
运行结果:
4330552352 (<cell at 0x102271050: int object at 0x101afb460>,)
4330917616 (<cell at 0x102271050: int object at 0x101afb480>,)
4330941184 (<cell at 0x102271050: int object at 0x101afb4a0>,)
4
4
4
2)不是闭包函数:
def my_func(*args):
fs = []
for i in range(3):
# func内部没有引用外部的变量,不是闭包函数
def func(_i=i):
return _i * _i
print(id(func), func.__closure__)
fs.append(func)
return fs
# 执行到这里,返回三个函数及对应的默认参数值
fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())
运行结果:
4330549760 None
4330941040 None
4330940608 None
0
1
4
二、Python 装饰器
装饰器(Decorators)是 Python 的一个重要部分,用来给其它函数添加额外功能(在不改变原函数代码的情况下)的函数。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等。
它还可以使代码变得更简洁。比如,如果要给某几个函数添加“计时”功能,你无需同时修改这几个函数的代码,只需定义一个计时函数,通过@符号添加装饰器即可。
另外,对于一个函数有多个装饰器的情况,通过调整装饰器的顺序,并设置断点查看运行过程,得到结论:
- 1.装饰器的加载顺序是:从下到上,装饰器的执行顺序是:从上到下。
- 2.更具体一点,多个装饰器之间是嵌套关系,按就近原则从里到外依次嵌套。
@transform_input @cal_time def func(*args): ... 等价于 transform_input_wrapper(*args) { result = cal_time_wrapper(*args) { result = func(*args) { ... } return result } return result }
代码示例:
from time import time, sleep
# 功能1.定义一个函数,用来计算函数func的时间
def cal_time(func):
print("自定义{统计时间}装饰器...")
# 引用外部的func变量,cal_time_wrapper是闭包函数
def cal_time_wrapper(*args):
print("{统计时间}装饰器的代码 before")
start_time = time() # time()获取系统当前时间
print("调用函数:", func.__name__)
result = func(*args)
end_time = time() # time()获取系统当前时间
print("{统计时间}装饰器的代码 after")
print("装饰器:{},{}".format(func.__name__, end_time - start_time))
return result
print("cal_time_wrapper()是否是闭包:", cal_time_wrapper.__closure__)
return cal_time_wrapper
# 功能2.定义一个函数,对输入参数进行转换,如果参数是float就转换为int
def transform_input(func):
print("自定义{转换参数}装饰器...")
# 引用外部的func变量,transform_input_wrapper是闭包函数
def transform_input_wrapper(*args):
print("{转换参数}装饰器的代码 before")
# 转换参数
args = tuple(int(param) if type(param) == float else param for param in args)
print("调用函数:", func.__name__)
result = func(*args)
print("{转换参数}装饰器的代码 after")
return result
print("transform_input_wrapper()是否是闭包:", transform_input_wrapper.__closure__)
return transform_input_wrapper
@transform_input
@cal_time
def test1(a, b, c, d):
# 睡眠1秒
sleep(0.1)
return (a + b + c + d) / 2
@cal_time
@transform_input
def test2(a, b, c):
sleep(0.1)
return (a + b + c) / 2
@cal_time
def test3(a, b):
sleep(0.1)
return (a + b) / 2
@cal_time
def test4(a):
sleep(0.1)
return a / 2
print("------")
print(test1(1, 2, 3.5, 4))
print("------")
print(test2(1, 2, 3))
print("------")
print(test3(1, 2))
print("------")
print(test4(1))
运行结果:
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054fa3d0: function object at 0x1054d78c0>,)
自定义{转换参数}装饰器...
transform_input_wrapper()是否是闭包: (<cell at 0x1054fa410: function object at 0x1054d7830>,)
自定义{转换参数}装饰器...
transform_input_wrapper()是否是闭包: (<cell at 0x1054fa510: function object at 0x1054cc200>,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054faf90: function object at 0x1054cc0e0>,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054fafd0: function object at 0x1054c7dd0>,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054fc050: function object at 0x1054c7cb0>,)
------
{转换参数}装饰器的代码 before
调用函数: cal_time_wrapper
{统计时间}装饰器的代码 before
调用函数: test1
{统计时间}装饰器的代码 after
装饰器:test1,0.10467410087585449
{转换参数}装饰器的代码 after
5.0
------
{统计时间}装饰器的代码 before
调用函数: transform_input_wrapper
{转换参数}装饰器的代码 before
调用函数: test2
{转换参数}装饰器的代码 after
{统计时间}装饰器的代码 after
装饰器:transform_input_wrapper,0.10127425193786621
3.0
------
{统计时间}装饰器的代码 before
调用函数: test3
{统计时间}装饰器的代码 after
装饰器:test3,0.10306096076965332
1.5
------
{统计时间}装饰器的代码 before
调用函数: test4
{统计时间}装饰器的代码 after
装饰器:test4,0.10345077514648438
0.5