为什么Python 3中的列表推导闭包会引发`NameError`,而在Python 2中却不会?

4

在从Py2升级到Py3的代码中,我遇到了一个奇怪的问题,我无法解释。类定义中的列表推导式可以在Python 2和3中都作为for子句引用其他类级变量。然而,如果这些变量是if子句的一部分,则它们会在Python 3中抛出NameError,但在Python 2中却可以正常工作。

我找到了一些相关的问题,但它们似乎并不能完全解释这个问题。在定义中引用其他类属性的字典类属性是一个类似的问题,在lambda中,但似乎不依赖于Python版本。此外为什么列表推导中会有NameError?,但与类的范围有关而非调试器。

以下代码在Python 2.7.16上可以正常工作,但在Python 3.7.4上失败:

class A(object):
    a = [1, 2, 3]
    b = [n for n in a if n in a]

print(A.b)

在 Python 2 中,我得到了:

[1, 2, 3]

在Python 3中,我得到:

Traceback (most recent call last):
  File "list_comprehension_closure.py", line 3, in <module>
    class A(object):
  File "list_comprehension_closure.py", line 5, in A
    b = [n for n in a if n in a]
  File "list_comprehension_closure.py", line 5, in <listcomp>
    b = [n for n in a if n in a]
NameError: name 'a' is not defined

然而,以下内容在Python 2和3中都可以正常工作,唯一的区别在于if子句。
class A(object):
    a = [1, 2, 3]
    b = [n for n in a]

print(A.b)

此外,以下内容在python 2和3中都适用,唯一的区别是推导式定义在class块之外:

a = [1, 2, 3]
b = [n for n in a if n in a]

print(b)

我知道Python 3中的闭包和列表解析有一些变化,但我没有看到任何可以解释这种差异的地方。 编辑: 为了更清晰,我不是在寻找解决方法。正如我最后一个例子所示,我知道将变量移出类作用域可以解决问题。然而,我想知道的是为什么Python 3中的行为发生了改变。

将 Python 2 代码复制到你的 Python 3 文件中,然后再次运行它。 - U13-Forward
@U10-Forward 虽然在发布后的10分钟内发现Python的任一版本都有重大变化会让我感到惊讶,但为了做一个好运动员,我再次在Python 2和3中运行了相同的代码,并得到了相同的结果:> cat list_comprehension_closure.py class A(object): a = [1, 2, 3] b = [n for n in a if n in a] print(A.b) > python2.7 list_comprehension_closure.py [1, 2, 3] > python3.7 list_comprehension_closure.py ... NameError: name 'a' is not defined - jbryan
@hunzter 谢谢,我在搜索中没有看到那个问题。然而,它并没有完全解决这个问题。解释为什么第一个例子,在if语句中引用a会失败,这是有道理的。我可能在字节码分解方面漏掉了一些东西,但我的理解是,我的第二个例子,在for语句中仅引用a 应该 在Python 3中失败,但它没有。 - jbryan
@hunzter,仔细阅读后,我发现链接的问题确实解释了为什么我的第二个示例有效。 “无论Python版本如何,推导式或生成器表达式的一部分都会在周围的范围内执行。这将是最外层可迭代对象的表达式。” - jbryan
1个回答

2
对我来说,这似乎是变量 a 作用域不当的问题。以下代码通过将 a 放在更高的作用域中来解决问题:

a = [1, 2, 3]
class A(object):
    b = [n for n in a if n in a]

print(A.b)

我知道有解决方法,实际上为了让代码能够运行,我采用了你提出的第一种方法的变体。你提出的第二种方法在两个Python版本中都不起作用,因为当A被引用时它还没有被定义。在Python 2和3中都会抛出“NameError:name 'A' is not defined”。但是,我正在尝试理解Python 2和3之间发生了什么变化,可以解释这种行为上的差异。 - jbryan
1
我删除了第二部分,因为你是对的,它不起作用。我可能没有先清除所有内容。我不知道原因。 - user11563547

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接