最近学习Python学到了闭包和装饰器,对这个Python语言的特别用法很不理解,在网上查找了大量资料后,有了些自己的理解。
知识引入
Python语言的特点之一就是可以采用嵌套定义的方式,即在一个函数的函数体中定义另外一个函数。而闭包和和修饰器都是在此之上建立的。
闭包
先来看一个例子,这是一个最简单的闭包
def outer():
print("这是outer的前面")
num=1
def inner():
print("这是inner")
print(num)
print("这是outer的后面")
return inner
if __name__ == '__main__':
outer()
'''
输出结果为:
这是outer的前面
这是outer的后面
'''
f=outer()
f()
'''
输出结果为:
这是inner
1
这是因为在前一句代码中f=outer中,outer函数的返回值为inner函数。
同时由于内层函数还未结束,外层函数的自由变量被绑定到了内层函数中,
所以可以直接输出num
'''
这里我画了一张图,应该能更好的理解闭包这个概念。
若使f=fn1(),则代码此时的执行顺序为:
A→B→return fn2
可见fn2作为返回这绑定到了f上,此时f就相当于fn2,同时由于fn2为fn1的内层函数,fn1在执行完毕后A和B代码块的自由变量并没有被释放,被绑定到了fn2里,所以fn2可以使用fn1中的变量。
闭包是指延伸了作用域的函数,在其中能够访问未在函数定义体中定义的非全局变量。
自由变量是指在一个函数内部被引用但不在函数内部直接定义或绑定的变量。 它们是从函数的外部环境中获取的。
闭包的主要作用在于可以封存函数执行的上下文环境,可以保存外层函数的数据和环境,并且每个闭包可以被多次调用。
def outer(x):
y=2
def inner(z):
return x+y+z
return inner
if __name__ == '__main__':
f1=outer(10)
'''将inner返回给f1,此时x=10,y=2'''
f2=outer(100)
'''将inner返回给f2,此时x=100,y=2'''
print(f1(5))
'''输出f1,此时x=10,y=2,z=5,结果为17'''
print(f2(50))
'''输出f2,此时x=100,y=2,z=50,结果为152'''
装饰器
闭包给我们带来的便利有很多,其中最大的就是装饰器的使用。
利用修饰器,可以在不修改已有函数的情况下向已有函数中注入代码,使其具备新的功能。一个装饰器可以为多个函数注入代码,一个函数也可以注入多个装饰器的代码(按顺序注入)。
装饰器最大的优点就是可以再不修改原函数的情况下添加新功能,并且不需要在每个引用处添加,只需要在定义处添加即可实现添加。
装饰器为什么需要闭包?
这个问题也是困扰我的主要问题,如果想要做到修饰器的效果的话,只需要利用Python可以让函数作为参数传递的特点就可以实现。如下所示
def oldFn():
print("这是一个旧函数")
def decoratorFn(fn):
print("这是在老函数之前添加的方法")
fn()
print("这是在老函数之后添加的方法")
if __name__ == '__main__':
decoratorFn(oldFn())
可见这样也能够实现添加功能,但是问题来了,如果这个函数有多处调用,则我们需要在每一个有调用的地方都进行修改,过于麻烦,所以就有了
装饰器的诞生
def decoratorFn(func):
'''添加的功能'''
def wrapper():
'''添加的功能'''
print('before func')
func()
print('after func')
return wrapper
@decoratorFn
def fn1():
print('这是fn1')
@decoratorFn
def fn2():
print('这是fn2')
if __name__ == '__main__':
fn1()
fn2()
'''
运行结果为:
before func
这是fn1
after func
before func
这是fn2
after func
'''
decoratorFn函数就是装饰器的函数,使用装饰器时只要在待加函数上一行加@装饰器函数名即可,如上代码中就是@decoratorFn。加上了@decoratorFn后,调用新功能的fn1函数就和原来的调用一样,也意味着所有调用这些函数的地方不需要修改就可以升级。
在这里@的作用就相当于fn1=decoratorFn(fn1),所以fn1可以直接使用
但是这里演示的都是没有参数的,如果是有参数的原函数的话,内部函数中必须要有能够接收原函数参数的的变量。由于我们在设计装饰器的时候更考虑通用性,所以内部函数一般都是设置成所有参数都能接收,即(*args, **kwargs)
def decoratorFn(func):
def wrapper(*args, **kwargs):
print('before func')
func(*args, **kwargs)
print('after func')
return wrapper
@decoratorFn
def fn1(a,b,c):
print('这是fn1')
print(a+b+c)
@decoratorFn
def fn2(a,b):
print('这是fn2')
print(a+b)
if __name__ == '__main__':
fn1(1,2,3)
fn2(4,5)
为了更清晰的认识含参原函数被装饰器装饰后参数的变化,我又画了张图来解释 :
含参装饰器
可以在装饰器外面再次封装,来接收装饰器的参数
给装饰器不同的参数,能够实现装饰器的各种用法:
def decoratorFnOuter(outerStr):
def decoratorFn(func):
def wrap(*args, **kwargs):
if(outerStr=="men"):
func(*args, **kwargs)
else:
pass
return wrap
return decoratorFn
@decoratorFnOuter('men')
def men():
print('men')
@decoratorFnOuter('woman')
def woman():
print("woman")
if __name__ == '__main__':
men()
woman()
可见装饰器外面又封装了一层,让内部装饰器可以拥有参数来进行判断和使用。以达成同一个装饰器在不同地方的不同用法。