Python中函数是一个对象, 和整数,字符串等对象有很多相似之处,例如可以作为其他函数的参数或返回对象, Python中的函数还可以携带自由变量, 两者无疑极大增进了Python的表达力.
但是Python函数自由变量的内部机制和列表解析或for循环结合使用时却暗藏杀机:
#---CASE 1 fs = map(lambda i:(lambda j: i*j),range(6)) print([f(2) for f in fs]) #---CASE 2 fs = [lambda j:i*j for i in range(6)] print([f(2) for f in fs]) #---CASE 3 fs = [] for i in range(6): fs.append(lambda j:i*j) if i==3: break print([f(2) for f in fs]) #---CASE 4 fs = [(lambda i:lambda j:i*j)(i) for i in range(6)] print([f(2) for f in fs])
结果:
[0, 2, 4, 6, 8, 10] [10, 10, 10, 10, 10, 10] [6, 6, 6, 6] [0, 2, 4, 6, 8, 10]
可以通过下面这个简单的测试来分析Python函数在执行时是如何确定自由变量的值的:
i = 1 def f(j): return i*j print(f(2)) # ---> 2 i = 2 print(f(2)) # ---> 4 def g(): i = 3 def f(j): return i*j return f f = g() print(f(2)) # ---> 6 i = 100 print(f(2)) # ---> 6
可见,当 函数f在*定义时*, Python不会记录自由变量'i'对应什么对象, 只会告诉f, 你有一个自由变量, 它的名字叫 'i'.
接着, 当函数f在*执行时*, Python告诉f:
(1) 空间上: 你需要在你被*定义时*的外层namespace里面去查找i对应的对象, 假设这个namespace为X.
(2) 时间上: 是在你*当前运行时*, X 里面的 i 对应的对象.
上面那个简单测试中的 i = 2 之后, f(2)随之也返回4也能反映了这一点.
CASE 2和3 也是如此, fs里面每个函数对应的自由变量i在*定义时*都是循环变量i, 因此*执行时*都是对应循环结束或跳出时i所指对象.
而 CASE 1和4为什么能如愿发生变化呢? 这是因为函数对应的自由变量i不再是循环变量i, 而是外层lambda函数*执行时*,循环变量i所指对象在其栈上的拷贝, 由于每次调用外层lambda时i所指对象都不相同, 因此每个函数的自由变量也会指向不同的对象.
最后, 列表解析里面的作用域是一个全新的作用域, 而普通的for循环则有所不同. 例如:
#---CASE 2 fs = [lambda j:i*j for i in range(6)] print([f(2) for f in fs]) i = 4 print([f(2) for f in fs]) #---CASE 3 fs = [] for i in range(6): fs.append(lambda j:i*j) print([f(2) for f in fs]) i = 4 print([f(2) for f in fs])
结果是:
[10, 10, 10, 10, 10, 10] [10, 10, 10, 10, 10, 10] [10, 10, 10, 10, 10, 10] [8, 8, 8, 8, 8, 8]