先来做两个实验,加深一下对外层变量和内层变量的区别和关联:
>>> def funa():
x = 88
def funb():
x = 99
return print(x)
>>> funa()
88
----------
在这个案例中,虽然内层函数定义了另一个变量x,但最终返回的,仍然是外层函数的变量x。因此x = 88.
>>> def funa():
x = 88
def funb():
x = 99
print(x)
return funb()
>>> funa()
99
----------
在这个案例中,print(x)是funb()的一部分,因此最后返回的是funb()中定义的x,因此是99.
上面两个例子都有一个共同点,就是内层函数和外层函数都没有设定参数。如果函数设定了参数,会是怎么样的情况呢?这个等下举例的时候会提到。
闭包:如果一个内层函数调用了他的外层函数的参数。那么我们称这个内层函数和被他调用的参数为一个闭包。
举个例子:
>>> def funa(x):
def funb(y):
return x * y
return funb
>>> a = funa(9)
>>> a(6)
54
从上面例子可以看到,x这个外层函数的参数,在内层函数中被调用。最终返回的是funb,而非funb(),原因是funb这个内层函数是有参数y的,如果返回funb()就相当于缺少了y,没有给y这个参数赋值,最终就会导致报错。
>>> def funa(x):
def funb(y):
return x * y
return funb()
>>> a = funa(8)
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
a = funa(8)
File "<pyshell#16>", line 4, in funa
return funb()
TypeError: funb() missing 1 required positional argument: 'y'
一开始我完全没有看懂为什么需要输入一个a = funa(8),然后在输入一个a(9),后来明白了。输入funa(8)相当于调用了funa(x),并且将x赋值为8,外层函数内部嵌套了一个内层函数funb(y),运行到这里的时候,就需要你给funb(y)里面的y一个赋值。那么这个赋值要如何赋呢?肯定不能直接funb(9)这样子,因为funb()作为一个内层函数,是不能在全局环境中直接调用的。
而程序已经通过设计解决了这个问题。外层函数返回的结果本身就是funb,此时的funb就等于a(外层函数返回的结果),那么a(9)自然也就相当于funb(9)了。
其实这么写可能更简单一些:
>>> def funa(x):
def funb(y):
return x * y
return funb
>>> funa(8)(9)
72
教科书上没有这么写,这是我通过理解之后推导出来的哦。我可真棒!!
正如上一节所说,python并不希望你在函数内部修改全局变量,或者在内层函数中修改外层函数的变量。可如果你一意孤行,那也不是不能改,只是在内层函数中对变量赋值的时候,要做一下全局声明,也就是要加上一个global。
在闭包中也是如此,如果你想在内层函数中对外层函数的变量进行修改,你需要先对变量进行一个声明,即在要修改的变量名前面加上nonlocal。例:
>>> def funa():
x = 5
def funb():
nonlocal x
x = x + 1
return x
return funb
>>> funa()()
6
当然,这是python3中的用法,而在python2中可没有这么智能。我们说外层函数中x=5,内层函数中,x=6,这时候内层函数并不是改变了外层函数的变量值,而是重新生成了一个名称相同但id不同的变量x,并给他赋值为6。为什么要这样操作?因为在这个过程中,x这个变量是放在栈里的。因此要创造一个新的x,来避免将原来的x所覆盖。
可我们有的时候就是要覆盖原来的x,就是要在内层函数中修改外层函数的变量值。那咋办呢?可能你已经想到主意了。我们找一个不放在栈里的东西来表示变量x不就行了吗?
那么那种类型的数据是不放在栈里的呢?序列!
因此,假如我们让外层函数的x = [5],在内层函数中,让x[0] = x[0] + 1这就完美地逃脱了原来规则的束缚。举例如下:
>>> def funa():
x = [5]
def funb():
x[0] = x[0] + 1
return x[0]
return funb
>>> funa()()
6
由于python3已经帮我们解决了这个问题了,因此这个投机取巧的办法学习其思想,能看懂,就OK了。