在看这部分内容之前,我需要大家了解函数的概念。这个应该不难吧,有一点点编程基础的都应该知道。
闭包
什么是闭包?
说白了就是函数嵌套的时候,内部函数用了外部函数的变量,那个内部函数就叫闭包。
比如这样的一段代码中,那个fun2就是闭包。
def fun1(start=0):
count = [start]
def fun2():
count[0] += 1
return count[0]
return fun2
quote = fun1()
print(quote())
因此可以知道,闭包需要满足的三个条件:
- 存在于嵌套关系的函数中
- 内部函数引用了外部函数的变量
- 外部函数将内部函数名作为返回值返回
闭包有什么用?
说白了就是让闭包活下来。
空说也没有用,看下面这一段代码:
def fun():
a = 2
quote = fun()
print(quote)
运行肯定报错!
为什么?因为我没有返回那个变量a,那么变量a就会在fun()函数执行完被销毁。
再看这一段:
def fun():
a = 2
return a
quote = fun() # quote = a
print(quote)
运行结果:
2
为什么?因为fun()的返回值是a,然后a又被赋值给quote,因此a被返回,也就被保留了下来。
闭包是同样的道理,看文章开头的那段代码:
def fun1(start=0):
count = [start]
def fun2():
count[0] += 1
return count[0]
return fun2
quote = fun1()
# 执行完fun1()这个函数后,返回的是fun2这个函数名。
# 所以此时相当于`quote = fun2`
print(quote())
# 这次再执行quote(),就相当于执行fun2()。
# fun2就这样被保留了下来
但是,我现在理解了闭包的逻辑了,然后呢?闭包有什么用?
我上边那代码直接这样不就好了吗:
def fun1(start=0):
return start + 1
print(fun1())
到这里不知道大家看没看出区别:
- return 变量、return 函数,本质上是没有区别的
- return 变量只能返回一个变量,return 函数可以返回的东西就多了,或者说可以留住的东西就多了
- return 函数,这个函数将会一直存活,这个函数内部的变量也会一直存活,那么能做的事情就多了…
这就是闭包的意义所在!它把数据私有化,完成了一次封装,相当于面向对象。
还是用开头的例子举例:
def fun1(start=0):
count = [start]
def fun2():
count[0] += 1
# 我们把代码做一些修改,直接在内部函数打印变量值
print(count[0])
return fun2
quote = fun1() # quote = fun2
quote() # fun2()
quote()
quote()
猜猜结果会是什么?这下是不是更加深刻地理解了闭包的作用了?
图解

- 本来变量a的存活域只是在outer函数中
- 然后inner函数将a私有化,然后通过outer返回inner的方式,整个inner被调到了外边
- 就像函数返回一个数字或字符串一样,这个inner会一直存活在整个程序中,同时inner中的变量也都会在整个程序中存活
装饰器
什么是装饰器?
**在不影响原有函数功能的情况下,添加新的功能。**就是装饰器的作用,此话怎讲呢?这要从闭包说起!
因为,如果一个闭包函数,的外层函数,的参数是一个函数的话,这就构成了装饰器
一个最基本的闭包结构是这样的:
def outer():
/* codes */
def inner():
/* codes */
return inner
如果此时,我们外部有个函数test()需要扩展功能,就需要在test()前边使用装饰器,这里需要注意3点:
- 在test()前边加上
@outer,这个的意思就是把test作为参数传入outer()中 - 外部函数outer需要接收一个函数作为参数
- 内部函数inner需要将fun()作为参数返回
所以代码实现应该是这样的:
def outer(fun): # outer需要传入一个fun作为参数
/* codes */
def inner():
/* codes */
return fun() # inner返回fun()
return inner
@outer # test前加上 @outer
def test():
print('test!')
这就是装饰器!
那么,return fun和return fun()有什么区别呢?
- return fun返回的是函数对象,函数名
- return fun()返回的是一个函数调用,也就是说在返回的过程中fun()将被调用,其实真正意义上返回的是fun()执行之后的结果
装饰器是如何工作的?
def outer(fun):
/* codes */
def inner():
/* codes */
return fun()
return inner
# @outer的意思就是,在执行test()函数前,先执行一遍outer()函数,并且把test作为参数传入outer
# 根据闭包的知识我们知道,outer的执行结果是inner,而此时inner的执行结果是fun(),所以
# @outer -> outer() -> inner() -> fun()
@outer
def test():
print('test!')
test()
把代码补全一下:
def outer(fun):
print('outer!')
def inner():
print('inner!')
return fun()
return inner
@outer
def test():
print('test!')
test()
看下执行结果:
outer!
inner!
test!
是不是就像上边说的,执行过程就是这个outer() -> innner() -> fun(),先死记硬背下来!
装饰器工作原理
当我们执行test()时,会分为两个过程:
首先会执行@outer
等价于 test = outer(test) = inner
然后会执行test()
等价于 inner()
而inner()的结果是返回fun(),fun()是函数调用的意思,所以会执行一下fun()
所以,根据这个过程,就可以知道函数具体的执行过程了:
先执行outer(),打印"outer!",然后返回inner函数对象
再执行inner(),打印"inner!",然后返回fun()函数调用
再执行fun(),也就是test(),打印"test!"
这就好了呀,你就记住执行过程就是outer() -> innner() -> fun()不就完了!
装饰器带参数
这个直接用一个例子来看一下,因为原理和不带参数的是一样的,这里我要求你们背下来两个块内容:语法格式、执行过程。
大家其实不用对“背”这个字那么敏感,我也没说真的是死记硬背对不对,原理上边已经说的很清楚了。
有时候之所以掌握不好一部分内容,就是太忌讳背代码这件事了。背下来不代表不理解它的逻辑,反而还会帮助你理解。
会背了用起来就熟练了,用着熟练了理解就更透彻了,真的是这样。
题目:
- 写两个函数,功能就是打印一句话“不管你几岁,赶紧关注我!”;
- 在不改变原有函数的基础上,根据年龄再打印一句话,年龄小于14岁打印“未满14岁,不能谈恋爱!”,另一个打印“已满14岁,可以谈恋爱了”;
解答:
题目的意思就是有两个函数,一个函数代表小于14岁,另一个代表大于14岁,首先这两个函数都是有原有功能的,但是博主非常不要脸,让它们原有的功能全都是打印一句话“不管你几岁,都快点关注我,顺便再点个赞,或者关注下同名公众号「白帽子续命指南」!”,真是,太不要脸了,但也没办法。
先把这两个函数写出来:
def young():
print('不管你几岁,都快点关注我!')
def old():
print('不管你几岁,都快点关注我!')
young()
old()
第二个要求就是判断年龄,然后各打印一句话,还是不难,但需要用装饰器完成。
根据装饰器的原理,我们知道,它会先执行外层函数,再执行内层函数,最后执行被装饰函数。而且被返回到整个程序作用域的肯定是内层函数,所以这个功能应该在内层函数中实现。
据此,写出闭包函数(注意装饰器的格式):
def outer(fun):
def inner():
if age < 14:
print('不满14岁,不能谈恋爱!')
if age > 14:
print('已满14岁,建议好好学习!')
return fun()
return inner
age = 10 # 不知道在哪传age,用不能没有age,就随便设一个age
@outer
def young():
print('不管你几岁,都快点关注我!')
@outer
def old():
print('不管你几岁,都快点关注我!')
young()
old()
差不多就是这样,但是那个age怎么传进去呢?这里就要科普一个知识点了:
装饰器是可以传进去参数的,它默认会把被装饰函数作为参数传进去,那如果我们再手动传一个参数进去,应该也是可以的,试试:
def outer(fun,age): # 这里把默认的参数,还有手动上传的参数全都写上
def inner():
if age < 14:
print('不满14岁,不能谈恋爱!')
if age >= 14:
print('已满14岁,建议好好学习!')
return fun()
return inner
@outer(age=10) # 手动传第一个参数进去
def young():
print('不管你几岁,都快点关注我!')
@outer(age=18)
def old():
print('不管你几岁,都快点关注我!')
young()
old()
看起来应该没问题,运行一下:
Traceback (most recent call last):
File "/Users/c0ny100/Documents/python_codes/my_python/闭包和解释器.py", line 12, in <module>
@outer(age)
TypeError: outer() missing 1 required positional argument: 'age'
报错了!
说明不可以这样传参,那怎么传?
**再套一层!**在outer外边再套一层,那一层就专门用来接收参数,然后outer和inner的逻辑不变,这样就可以了!
def parm(age): # 再来一层,专门用来接收参数
# 这部分逻辑都是不变的
def outer(fun):
def inner():
if age < 14:
print('不满14岁,不能谈恋爱!')
if age >= 14:
print('已满14岁,建议好好学习!')
return fun()
return inner
return outer # 别忘了,因为又加了一层,所以得返回一下outer
# 而且除了inner返回的是函数调用以外,其余返回的都是函数名
@parm(age=10)
def young():
print('不管你几岁,都快点关注我!')
@parm(age=18)
def old():
print('不管你几岁,都快点关注我!')
young()
old()
运行一下,完美!OK,把上边这个成品背下来!
不满14岁,不能谈恋爱!
不管你几岁,都快点关注我!
已满14岁,建议好好学习!
不管你几岁,都快点关注我!
被装饰函数带参数
这个更简单,把参数传到inner里就完了。
举个例子,本来test函数是计算a+b的值,但现在我们想知道(a+1)+(b+1)的值,于是使用装饰器来实现:
def outer(fun):
def inner(a, b):
a += 1
b += 1
return fun(a, b)
return inner
@outer
def test(a, b):
print(a + b)
test(1, 1)
运行结果:
4
有没有大牛给我解释一下,为什么执行的是test(1, 1),结果却是4?也就是为什么1+1=4?[doge]
这个看起来没什么用,但当我们调用某些数学模块,你想改变某些值,又不想改变他本身的算法的时候,还是可以用到的。比如有一个骚操作时,在inner里把a、b的类型改了:
def inner(a, b):
a = 'a'
b = 'b'
return fun(a, b)
这都是可以的。
除此之外还有调用多个装饰器以及被装饰函数有返回值的情况,原理都一样,就不细说了。遇到了自己百度吧,应该可以看懂了。
总结
闭 包:把内层函数整个保留下来
装饰器:从最外层函数开始执行,直到被装饰函数执行完
949

被折叠的 条评论
为什么被折叠?



