Python闭包没有按预期工作

时间:2011-05-17 19:09:45

标签: python lambda closures

当我运行以下脚本时,两个lambda都在同一个文件--junk.txt上运行os.startfile()。我希望每个lambda使用值“f”设置为lambda创建时。有没有办法让这个功能像我期望的那样发挥作用?

import os


def main():
    files = [r'C:\_local\test.txt', r'C:\_local\junk.txt']
    funcs = []
    for f in files:
        funcs.append(lambda: os.startfile(f))
    print funcs
    funcs[0]()
    funcs[1]()


if __name__ == '__main__':
    main()

2 个答案:

答案 0 :(得分:22)

一种方法是:

def main():
    files = [r'C:\_local\test.txt', r'C:\_local\junk.txt']
    funcs = []
    for f in files:
        # create a new lambda and store the current `f` as default to `path`
        funcs.append(lambda path=f: os.stat(path))
    print funcs

    # calling the lambda without a parameter uses the default value
    funcs[0]() 
    funcs[1]()

否则在调用函数时会查找f,因此您将获得当前(循环之后)值。

我更喜欢的方式:

def make_statfunc(f):
    return lambda: os.stat(f)

for f in files:
    # pass the current f to another function
    funcs.append(make_statfunc(f))

或甚至(在python 2.5 +中):

from functools import partial
for f in files:
    # create a partially applied function
    funcs.append(partial(os.stat, f))

答案 1 :(得分:4)

重要的是要理解当变量成为闭包的一部分时,它本身就是变量,而不是包含的值。

这意味着在循环中创建的所有闭包都使用完全相同的变量f,在循环结束时将包含循环内使用的最后一个值。

由于语言是如何定义的,但是这些捕获的变量在Python 2.x中是“只读”的:任何赋值都使变量成为局部变量,除非它被声明为global(Python 3.x添加了{{1}允许写入外部作用域的本地的关键字。

正如Jochen Ritzel在他的回答中所说的那样,避免这种变量捕获并获得价值捕获的常用习惯是写

nonlocal

这是有效的,因为默认参数值是在函数创建时计算的,而lambda f=f: os.startfile(f) 不是外部变量,而是一个函数参数,它将你想要的值作为默认值(所以这个lambda只是一个默认的函数参数的值,不再关闭任何词法变量。)