函数作为返回值
在 python 中,返回值为一个函数名的函数被称为 高阶函数
而所谓返回一个函数,一般情况下即意味着:执行这个函数并输出相应结果:
# 定义一个返回值为函数的函数
def myF1():
def myF2():
print("In myF2")
return 2
return myF2
# 使用上面定义
# 调用myF1, 返回一个函数myF2,赋值给f2
f2 = myF1()
f2()
上述代码的执行结果为:
In myF2
2
其实质是:调用了 myF1, 返回一个函数 myF2,赋值给了 f2,则 f2 的执行结果即为 myF2() 函数的执行结果
复杂一点的高阶函数
上述例子中,作为返回值的函数并没有和其所处于的外部函数有过多的关联。
当作为返回值的函数使用了其所处的外部函数的参数及局部变量时,即出现了闭包:
# args:参数列表
# 1 myF4定义函数,返回内部定义的函数myF5
# 2. myF5使用了外部变量,这个变量是myF4的参数
def myF4( *args):
def myF5():
rst = 0
for n in args:
rst += n
return rst
return myF5
f5 = myF4(1,2,3,4,5,6,7,8,9,0)
f5()
调用 f5,结果为:
45
闭包:
定义:
一个函数在其内部定义函数,并且内部的函数使用了外部函数的参数或者局部变量,当内部函数被作为返回值的时候,相关的参数和变量将保存在作为返回值的函数中,这种结构,叫做闭包。
闭包结构有以下特性:
- 返回的函数并不会立刻执行,它会保留所使用的相关的参数和变量,在调用的时候才会被执行
所谓 “相关的参数和变量将保存在作为返回值的函数中” ,其实际是传址操作而非传值,即保存在函数里的参数的值并不是不会改变的
这些特性会在典型的闭包会遇到的坑中体现:
来看案例:
def count():
# 定义列表,列表里存放的是定义的函数
fs = []
for i in range(1,4):
# 定义了一个函数f
# f是一个闭包结构
def f():
return i*i
fs.append(f)
return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())
上述例子中,我们定义了一个 count 函数,其中的 for 循环将执行三次,每次调用都会将一个 f(i) 函数放入 fs 这个列表中作为新的一项。
因为 fs[] 是一个以函数作为元素的列表, 所以这里构成闭包的还是最外层返回 fs[] 的 count 函数。
接下来的赋值语句。使用了可迭代数据的特性,将 count() 函数调用后得到的列表分拆为三个元素,分别赋值给 f1 , f2 , f3 。则此三个变量实际为函数变量
按照一般的理解,三次循环时的 i 值分别为 1,2,3 。则上述代码的执行结果应该为 1 4 9
但实际上…… 都说了有坑那肯定不是以为的那样。
实际结果:
原因在于上述的第二点特性。
作为返回值的函数 (fs 列表中的元素)使用了循环变量 i,因为返回的函数不会立即执行,因此,当三个函数都被返回时,i 的值已经变成了 3,因此在调用的时候,i 已经是 3。
此问题描述为:
返回闭包时,返回函数不能引用任何循环变量
注意哦,这里的 “f1,f2,f3 = count()” 与该特性无关,只是一个普通的赋值而已。
关于保存在各个返回函数中的参数 i 的值的变化过程可参考下图:
(自己拿 ppt 画的,凑合看,谁没有个第一次。。)
可以看到,每一次循环之后,可以看做是生成了一个偏函数(即参数固定的函数),而这个参数 i 的值一直在变化,到了执行的时候,就已经是 3 了。
其解决办法是,再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,即 将传址变成传值,切断函数与会变化的循环参数 i 的关联。
# 修改上述函数
def count2():
# 在外面再套一层
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1,4):
fs.append(f(i))
return fs
f1,f2,f3 = count2()
print(f1())
print(f2())
print(f3())
其执行结果:
1
4
9