【问题标题】:Weird closure behavior in pythonpython中奇怪的闭包行为
【发布时间】:2012-06-19 21:37:07
【问题描述】:

我有以下简单代码:

def get():
    return [lambda: i for i in [1, 2, 3]]

for f in get():
    print(f())

根据我的 Python 知识,输出为 3 - 整个列表将包含 i 的最后一个值。但这在内部如何运作?

AFAIK,python 变量只是对对象的引用,所以第一个闭包必须先包含对象 i 引用 - 这个对象肯定是 1,而不是 3 O_O。 python 闭包如何包含变量本身而不是对象这个变量引用?它将变量名称保存为纯文本、一些“对变量的引用”还是什么?

【问题讨论】:

    标签: python closures


    【解决方案1】:

    正如@thg435 指出的那样,lambda 不会在那个时候封装值,而是封装范围。解决这个问题的方法太少了:

    lambda 默认参数“hack”

    [ lambda v=i: v for i in [ 1, 2, 3 ] ]
    

    或者使用 functools.partial

    from functools import partial
    [ partial(lambda v: v, i) for i in [ 1, 2, 3 ] ]
    

    本质上,您必须将范围移动到您正在创建的函数的本地。一般来说,我喜欢更频繁地使用partial,因为你可以将它传递给一个可调用对象,以及任何 args 和 kargs 来创建一个具有适当闭包的可调用对象。在内部,它包装了您的原始可调用对象,以便为您转移范围。

    【讨论】:

    • +1 对于在此处使用 partial,我认为这是一种更简洁的方法。不过,您可能想要编辑您的代码,根据 PEP-8,列表括号内的空格是不好的。 (我知道你在关注提问者,我在那里进行了编辑)。
    • @Lattyware:谢谢! lambda 很酷,但我总是觉得它们读起来更乱。 partial 感觉更具可读性和便携性。
    • 100% 同意,我尽量避免使用 lambda,这是一个主要情况,partial 更清晰、更简单。
    【解决方案2】:

    闭包不是指变量,而是指作用域。由于i 在其范围内的最后一个值是'3',所以所有三个闭包都返回相同的值。要“锁定”变量的当前值,只需为其创建一个新范围:

    def get() : return [ (lambda x: lambda: x)(i) for i in [ 1, 2, 3 ] ]
    for f in get() : print( f() )
    

    【讨论】:

    • 是否有一些关于该主题的附加信息,以便我可以看到哪些隐藏变量用于“保存”范围、访问范围等?
    • (lambda x: lambda : x)(i) 是我在 Python 中见过的最丑陋的事情之一。伊克。 (并不是说您的答案是错误的或不好的,就其本身而言,只是说 - 很难阅读)。
    • @EyeofHell:我认为pep-227 是关于python 范围规则的规范文档。此外,在 SO 上有一些很好的答案,例如here
    • @Lattyware: de gustibus non est disputandum.
    • @GregE。我不认为在 Python 中做得很好是不可能的。我认为 jdi 对 functools.partial()lambda 混合的回答是最好的解决方案,它以更好的方式描述了正在做的事情 - 从我的角度来看。
    【解决方案3】:

    每个lambda 实际上都指向同一个i,这是一个由列表推导式创建的变量。列表解析终止后,i 将保留分配给它的最终元素的值,直到它超出范围(通过将其封装在函数中并返回它来防止,即lambda)。正如其他人所指出的,闭包不维护值的副本,而是维护对其范围内定义的变量的引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-30
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多