22

我刚刚阅读了这个问题的答案: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吗?

4

2 回答 2

20

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

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

<listcomp>代码对象寻找那个参数:

>>> 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调用引用传入的第一个也是唯一的位置参数;它在这里没有命名,因为从来没有一个函数定义来给它命名。

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

如果您回到我对该问题的回答,请查找标题为“(小)异常”的部分;或者,为什么一部分仍然可以工作;我试图在那里涵盖这一点:

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

于 2014-03-27T15:32:48.027 回答
-1

答案很有趣,dis.dis但实际上并没有解释为什么会发生这种情况。这是一个类似的错误

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

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

来源:python文档

于 2021-03-23T12:10:47.983 回答