类作用域在 Python 3 中有点奇怪,但这是有充分理由的。
在 Python 2 中,迭代变量 (i
and j
在您的示例中)泄漏出列表理解,并将包含在外部范围中。这是因为它们是在 Python 2 设计的早期开发的,并且基于显式循环。作为一个示例来说明这是如何意外的,请检查以下值B.i
and B.j
在 Python 2 中你没有收到错误!
在 Python 3 中,更改了列表推导式以防止这种泄漏。它们现在使用一个函数(有其自己的作用域)来实现,该函数被调用以生成列表值。这使得它们的工作方式与生成器表达式相同,而生成器表达式一直是隐藏的函数。
这样做的结果是,在类中,列表理解通常看不到任何类变量。这与无法直接查看类变量的方法是平行的(尽管self
或明确的类名)。例如,调用下面类中的方法将给出相同的结果NameError
您在列表理解中看到的异常:
class Foo:
classvar = "bar"
def blah(self):
print(classvar) # raises "NameError: global name 'classvar' is not defined"
然而,有一个例外:第一个序列正在迭代for
列表推导式的子句是在内部函数之外求值的。这就是为什么你的A
类在 Python 3 中工作。它这样做是为了让生成器可以立即捕获不可迭代的对象(而不是仅当next
调用它们并且运行它们的代码)。
但对内在不起作用for
课堂上二级理解中的子句B
.
如果您反汇编一些使用创建列表推导式的函数,您可以看到差异dis
module:
def f(lst):
return [i for i in lst]
def g(lst):
return [(i, j) for i in lst for j in lst]
这是拆解f
:
>>> dis.dis(f)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x0000000003CCA1E0, file "<pyshell#374>", line 2>)
3 LOAD_CONST 2 ('f.<locals>.<listcomp>')
6 MAKE_FUNCTION 0
9 LOAD_FAST 0 (lst)
12 GET_ITER
13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
16 RETURN_VALUE
前三行显示f
加载预编译的代码块并从中创建一个函数(将其命名为f.<locals>.<listcomp>
)。这是用于创建列表的函数。
接下来的两行显示lst
正在加载变量并从中创建迭代器。这发生在f
的范围,而不是内部函数的范围。然后<listcomp>
使用该迭代器作为其参数来调用函数。
这相当于类A
。它从类变量中获取迭代器integers
,就像您可以在新成员的定义中使用对先前类成员的其他类型的引用一样。
现在,比较一下拆解g
,它通过迭代同一个列表两次来配对:
>>> dis.dis(g)
2 0 LOAD_CLOSURE 0 (lst)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object <listcomp> at 0x0000000003CCA810, file "<pyshell#377>", line 2>)
9 LOAD_CONST 2 ('g.<locals>.<listcomp>')
12 MAKE_CLOSURE 0
15 LOAD_DEREF 0 (lst)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 RETURN_VALUE
这次,它使用代码对象构建一个闭包,而不是一个基本函数。闭包是一个带有一些“自由”变量的函数,这些变量引用封闭范围内的事物。为了<listcomp>
函数于g
,这工作得很好,因为它的范围是正常的。但是,当您尝试在类 B 中使用相同类型的理解时,闭包会失败,因为类不允许它们包含的函数以这种方式查看其作用域(如Foo
上面的类)。
值得注意的是,不仅内部序列值会导致此问题。正如在上一个问题BrenBarn 在评论中链接到,如果在列表理解的其他地方引用类变量,您将遇到相同的问题:
class C:
num = 5
products = [i * num for i in range(10)] # raises a NameError about num
但是,您不会从多级列表推导中得到错误,其中内部for
(or if
) 子句仅引用前面循环的结果。这是因为这些值不是闭包的一部分,只是闭包内的局部变量<listcomp>
函数的范围。
class D:
nested = [[1, 2, 3], [4, 5, 6]]
flattened = [item for inner in nested for item in inner] # works!
就像我说的,类作用域有点奇怪。