我要深刻反思一下,为什么最近几期的blog显得有一点点水而且风格诡谲了
都怪我找的那个Python网课,只开车不好好讲课!
所以深夜加班,重点谈一谈这个非常重要的内容——闭包
(尝试通过这次的讲解把Python函数部分的知识点串讲一ha~)
鸣谢alpha_panda大大~ 和 HAPPYEVERYD大大~
前辈的讲解都很深入详细,帮助我更好地理解这个难点啦
之前在blog中简单的提到了,闭包并不只是一个Python中的概念,ta在函数式编程语言中应用较为广泛
理解Python中的闭包一方面是能够正确的使用闭包,另一方面则可以好好体会和思考闭包的设计思想(有种和前辈心灵相通的感jio)
闭包到底是什么?
首先看一下维基上对闭包的解释:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
看不懂吧?巧了,我也看不懂。。。
我们之前解释了内(嵌)函数的意义,一言以蔽之:如果在一个函数的内部定义了另一个函数(注意一定是定义了一个船新的函数,不是简单的调用),外部的我们叫他外函数,内部的我们叫他内(嵌)函数。
一般我们对编程语言中函数的理解,大概是这样的:
- 程序被加载到内存执行时,函数定义的代码被存放在代码段中。
- 函数被调用时,会在栈上创建其执行环境,初始化其中定义的变量和外部传入的形参以便函数进行下一步的执行操作。
- 当函数执行完成并返回函数结果后,函数栈帧便会被销毁掉。函数中的临时变量以及存储的中间计算结果都不会保留。
- 下次调用时唯一发生变化的就是函数传入的形参可能会不一样。函数栈帧会重新初始化函数的执行环境。
而闭包已经不再是传统意义上定义的函数
闭包是一个内嵌函数,其定义中 引用了函数外定义的变量 ,并且该函数 可以在其定义环境外被执行
通俗来讲:我们在某个函数(外函数)中又定义了一个函数(内函数),内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用
闭包长什么样?
下面这段伪代码展示了闭包的基本结构:
def 外层函数(参数):
def 内层函数():
print("内层函数执行", 参数)
return 内层函数
内层函数的引用 = 外层函数("传入参数")
内层函数的引用()
闭包特点之一 . 外函数返回的是内函数的引用
引用是什么?
我们可以简单的把引用理解成地址(或者指向变量的指针?)
当我们在Python中定义一个函数def demo():
的时候,内存当中会开辟一些空间,存下这个函数的代码,内部的局部变量等等
然而demo
其实是一个变量名称,里面存了这个函数所在位置的引用
我们可以令x = demo
这就相当于把
里存的东西赋值给
,这样
就指向了
函数所在的引用
之后我们可以用
来调用我们创建的
(用我自己的理解来说,
就是函数
的一个别名)
对于闭包,在外函数outer
最后return inner
于是我们在调用外函数的时候就会返回inner
inner
是一个函数的引用,这个引用被存入了外函数outer
中
所以接下来在调用外函数outer
的时候,相当于运行了内函数inner
至此,我们有一个重要的发现:
如果函数名后紧跟一对括号,一般意味着调用此函数
如果缺省括号,相当于只是一个函数名称,里面存储了函数所在位置的引用
闭包特点之二 . 外函数会将临时变量绑定给内函数
按照我们正常的认知,一个函数结束的时候,会把自己的临时变量都释放
但是闭包是一个特别的情况:
外函数的临时变量会在将来的内函数中用到(闭包的特性)
于是在外函数结束,返回内函数的同时,会将外函数的临时变量与内函数绑定在一起
所以尽管外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量
下面的话就有一些晦涩难懂了:
我们只会定义一次内函数,但是在重复调用的时候,内部函数是能识别外函数的临时变量是不同的
Python中一切都是对象
虽然函数我们只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的
我们每次调用外函数,都会创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回
所以实际上我们每次调用外函数时,返回的都是不同的实例对象的引用,ta们的功能是一样的,但是并不是同一个函数对象
闭包实战!
通过实战,我们理解一下之前的泡泡茶壶
def outer(x):
def inner(y):
return x + y
return inner
>>> f=outer(7)
>>> f(2)
9
>>> f(5)
12
>>> g=outer(3)
>>> g(5)
8
>>> g(9)
12
>>> outer(4)(5)
9
注意!
外函数返回的是内函数的引用,没有括号!没有括号!没有括号!
我现在感觉闭包真是一个优美异常的结构
(当然我给出的是一个不能再简单的憨憨栗子)
有两种正确的调用姿势:
- 把内函数引用赋值给一个辅助变量,这个时候传入的参数就会直接绑定给内函数,之后只要调用这个辅助变量,就是默认该参数
- 双括号:
外函数名称(外函数参数)(内函数参数)
好像还有点问题?
如何在闭包中修改外函数局部变量?
在闭包内函数中,我们可以随意使用外函数绑定的临时变量,但是如果我们想修改外函数临时变量数值的时候就出事了。。。
在基本的python语法当中,一个函数可以随意读取全局数据,但是想要修改时有两种方法
- global声明全局变量
- 全局变量是可变类型
在闭包内函数也是类似的情况:
在Python3中,可以用nonlocal
关键字声明变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间寻找这个变量
(在Python2中,没有nonlocal
这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表)
def outer(x):
def inner(y):
nonlocal x
x*=x
return x + y*y
return inner
>>> f = outer(3)
>>> f(4)
25
闭包有什么用处?
-
装饰器&&单利模式
举个栗子:我们编写了一个登录功能,想统计这个功能执行花了多长时间。我们可以用装饰器装饰这个登录模块,装饰器会帮我们完成登录函数执行之前和之后取时间。 -
面向对象
之前提到:外函数会将临时变量绑定给内函数。
闭包是实现面向对象的方法之一。虽然我们在python中不这样使用,但是在其他编程语言(比如JavaScript)中,经常用闭包来实现面向对象编程。
听着别人翻唱的稻香&&有点甜(窝心。。。虽然唱的有些飞,但是好戳我泪点啊,感觉自己就是一个小白痴/笑哭)
我终于磨完了这篇blog(感天动地,一晚上都没有水群)
介绍的不浅不深,而且还有些地方一知半解需要完善,但是也算是尽力把知识有条理得讲了出来了吧
谢谢大家~