【问题标题】:Python closure not working as expectedPython 闭包没有按预期工作
【发布时间】:2011-08-27 12:13:53
【问题描述】:

当我运行以下脚本时,两个 lambda 都在同一个文件上运行 os.startfile() —— junk.txt。我希望每个 lambda 都使用创建 lambda 时设置的值“f”。有没有办法让它按我的预期运行?

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()

【问题讨论】:

标签: python lambda closures


【解决方案1】:

一种方法是这样做:

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))

【讨论】:

  • 这是可行的,因为默认参数是在函数定义时绑定的。
  • 啊,太棒了。生成的 lambdas 实际上被用作 PyQt 的事件处理程序,所以它们不能有任何输入参数,而且我目前被困在 python 2.4 中,所以我不能使用 functools,但第二种解决方案 - 将它传递给另一个功能——完美运行。谢谢。
  • @Brendan,如果你定义了 qt 槽,你应该可以有默认的输入参数,不是吗?如果您使用第二种解决方案,您可能还想应用装饰器“PyQt4.QtCore.pyqtSlot”。
【解决方案2】:

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

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

由于语言是如何定义的,但是这些捕获的变量在 Python 2.x 中是“只读的”:任何赋值都会使变量成为本地变量,除非它被声明为 global(Python 3.x 将 nonlocal 关键字添加到允许写入外部范围的本地)。

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

lambda f=f: os.startfile(f)

这是有效的,因为默认参数值是在函数创建时评估的,f 不是外部变量,而是一个函数参数,它将具有您想要的默认值(所以这个 lambda 只是一个具有默认值的函数参数,不再关闭任何词法变量)。

【讨论】:

    猜你喜欢
    • 2012-05-09
    • 2014-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多