在廖雪峰Python2.7教程的返回函数一节中,提到了闭包并举了这么一个例子:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
>>> f1()
9
>>> f2()
9
>>> f3()
9
全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
>>> def count():
... fs = []
... for i in range(1, 4):
... def f(j):
... def g():
... return j*j
... return g
... fs.append(f(i))
... return fs
...
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
//////////////// 以上是教程原文 ////////////////
关于引用循环变量的程序,我的个人理解是,在每一次循环中,通过f(j)函数定义并且将内部参数j传给了g()函数,同时,在每一次循环中通过fs.append(f(i))来传递循环变量并实际调用g(),然后将已经计算出结果的g函数返回保存至fs列表中,最后用f1,f2,f3变量指向fs中保存的三个函数,再通过调用f1(),f2(),f3()来调用函数。即,核心是通过添加f(j)函数来实现循环变量的传递。
也可以这么做:
>>> def count():
... fs = []
... for i in range(1, 4):
... def g(j = i):
... return j * j
... fs.append(g)
... return fs
...
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
在这里,循环变量i直接传递给了函数g()的内部变量j,即j绑定了循环变量,从而计算出了当前循环变量的对应结果,结果与原文中的一致,代码相对更加简洁明了。
但此处需要注意一点,在g()函数的定义中不能写成这样的形式:
... def g(i):
... return i * i
这样的写法,看似也可以将循环变量i直接传递进函数中进行计算,但实际上运行结果会是这样:
>>>f1()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (0 given)
这是什么原因?因为如果这样来定义函数的话,调用时不能直接调用, 必须传递一个参数,比如说f1(1),而不能像这样直接省略参数调用f1(),这样便违背了本意,因此,
... def g(j = i):
... return j * j
这一定义十分巧妙,为g()函数设置了默认参数,并将默认参数与循环变量绑定,又可以在后续的调用中直接调用,这个trick想必在以后写程序中都会经常用到。