为啥一个类变量没有在列表理解中定义,而另一个是?

Posted

技术标签:

【中文标题】为啥一个类变量没有在列表理解中定义,而另一个是?【英文标题】:Why is one class variable not defined in list comprehension but another is?为什么一个类变量没有在列表理解中定义,而另一个是? 【发布时间】:2014-05-06 16:46:06 【问题描述】:

我刚刚看了这个问题的答案:Accessing class variables from a list comprehension in the class definition

这有助于我理解为什么下面的代码会导致NameError: name 'x' is not defined

class A:
    x = 1
    data = [0, 1, 2, 3]
    new_data = [i + x for i in data]
    print(new_data)

出现NameError 是因为x 未在列表理解的特殊范围内定义。但是我无法理解为什么以下代码可以正常工作。

class A:
    x = 1
    data = [0, 1, 2, 3]
    new_data = [i for i in data]
    print(new_data)

我得到了输出[0, 1, 2, 3]。但我期待这个错误:NameError: name 'data' is not defined 因为我期待就像在前面的示例中一样,名称 x 没有在列表理解的范围内定义,同样,名称 data 也不会在列表理解的范围内定义范围。

您能帮我理解为什么x 没有在列表理解的范围内定义,而data 是吗?

【问题讨论】:

【参考方案1】:

data 是列表理解的来源;它是传递给创建的嵌套范围的一个参数。

列表解析中的所有内容都在单独的范围内运行(基本上作为函数),除了用于最左边的 for 循环的迭代。您可以在字节码中看到这一点:

>>> def foo():
...     return [i for i in data]
... 
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x105390390, file "<stdin>", line 2>)
              3 LOAD_CONST               2 ('foo.<locals>.<listcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_GLOBAL              0 (data)
             12 GET_ITER
             13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             16 RETURN_VALUE

&lt;listcomp&gt; 代码对象像函数一样被调用,iter(data) 作为参数传入(CALL_FUNCTION 使用 1 个位置参数执行,GET_ITER 结果)。

&lt;listcomp&gt; 代码对象查找那个参数:

>>> dis.dis(foo.__code__.co_consts[1])
  2           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                12 (to 21)
              9 STORE_FAST               1 (i)
             12 LOAD_FAST                1 (i)
             15 LIST_APPEND              2
             18 JUMP_ABSOLUTE            6
        >>   21 RETURN_VALUE

LOAD_FAST 调用指的是传入的第一个也是唯一的位置参数;它在这里没有命名,因为从来没有一个函数定义来给它命名。

在列表推导(或集合或字典推导,或生成器表达式,就此而言)中使用的任何其他名称都是局部变量、闭包或全局变量,而不是参数。

如果您返回my answer to that question,请查找标题为(小)异常的部分;或者,为什么一部分仍然可以工作;我试图在那里讨论这一点:

无论 Python 版本如何,推导式或生成器表达式的一部分都在周围范围内执行。那将是最外层可迭代的表达式。

【讨论】:

这是一个很好的答案,但它分解了一个函数,而不是一个类。对类没有意义...x不是A范围内的局部变量吗? @fmv1992:OP 链接到我回答的一个问题,涵盖了它在课堂上的工作原理。 除了我的答案之外,还要阅读这个答案,所以我认为重复自己没有任何意义。此处的反汇编说明了为什么 data 确实 起作用,我包括了一个反汇编以显示 如何 实现列表理解以说明为什么 data 不被视为变量父范围。无论范围如何,反汇编都是相同的,重点是展示它是如何在幕后工作的。【参考方案2】:

dis.dis 的答案很有趣,但它实际上并没有解释为什么会发生这种情况。这是,来自similar error:

如果名称绑定操作发生在代码块内的任何位置,则块内对名称的所有使用都将被视为对当前块的引用。如果在绑定之前在块中使用名称,这可能会导致错误。这个规则很微妙。 Python 缺少声明,并允许在代码块中的任何位置进行名称绑定操作。代码块的局部变量可以通过扫描块的整个文本进行名称绑定操作来确定。

所以简单来说:data 不能引用x,因为该块不受该点的约束。没有办法引用x:既不是单独的x,也不是A.x

来源:python docs。

【讨论】:

为什么会发生这种情况已经在 OP 链接到的问题中得到解答。请注意,这是我写的答案:Accessing class variables from a list comprehension in the class definition;我在那里讨论了同样的问题:列表解析中的所有内容都在单独的范围内运行,列表解析源除外。 更重要的是,OP 询问为什么这不适用于data,而不是x。原因是传递给iter()data 表达式的求值结果被传递到用于实现列表理解范围的“函数”中,因此不受有关父范围的规则的约束。 我没有看到其他问题。我相信如果答案是独立的会更好,否则,我们会从问题 A 到 B 再到 C...但是很好,感谢您的澄清。 我坚信我的回答独立的,它涵盖了这里提出的具体问题。我在为您提供问题中您可能错过的上下文。

以上是关于为啥一个类变量没有在列表理解中定义,而另一个是?的主要内容,如果未能解决你的问题,请参考以下文章

必须使用初始化成员列表的三种情况

为啥我不能从其他类访问公共变量?

需要解释为啥在定义类时使用一个冒号

JAVA,为啥final类不能被继承,如果定义为final的类该类里面成员变量不特殊说明则是final类还是非final

java中为啥要把main方法定义为一个static方法

在C语言中,为啥定义变量的时候总是初始化为0呢?该怎么理解。