首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >嵌套列表理解作用域

嵌套列表理解作用域
EN

Stack Overflow用户
提问于 2013-11-22 12:21:23
回答 1查看 2.9K关注 0票数 24

解释我的问题的最好方法是举个例子:

example.py:

代码语言:javascript
复制
class A(object):
    integers = [1, 2, 3]
    singles = [i for i in integers]

class B(object):
    integers = [1, 2, 3]
    pairs = [(i, j) for i in integers for j in integers]

当我在Python2下运行它时,它工作得很好,但在Python3下,我得到了B类的NameError (而不是A类):

代码语言:javascript
复制
$ python example.py
Traceback (most recent call last):
  File "example.py", line 6, in <module>
    class B(object):
  File "example.py", line 8, in B
    pairs = [(i, j) for i in integers for j in integers]
  File "example.py", line 8, in <listcomp>
    pairs = [(i, j) for i in integers for j in integers]
NameError: global name 'integers' is not defined

为什么只有B类会引发NameError,为什么只在Python3下?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-11-22 12:33:54

类作用域在Python3中有点奇怪,但这是有充分理由的。

在Python2中,迭代变量(示例中的ij )从列表理解中泄漏出来,并将包含在外部作用域中。这是因为它们是在Python2的设计早期开发的,并且它们是基于显式循环的。为了说明这是多么出乎意料,请检查Python2中没有出现错误的B.iB.j的值!

在Python3中,为了防止这种泄漏,对列表的理解进行了更改。它们现在是用一个函数(它有自己的作用域)实现的,调用该函数来生成列表值。这使得它们与生成器表达式的工作方式相同,生成器表达式一直是隐藏在幕后的函数。

其结果是,在类中,列表理解通常看不到任何类变量。这与不能直接看到类变量的方法类似(只能通过self或显式的类名)。例如,调用下面类中的方法将产生与您在列表理解中看到的相同的NameError异常:

代码语言:javascript
复制
class Foo:
    classvar = "bar"
    def blah(self):
        print(classvar) # raises "NameError: global name 'classvar' is not defined"

但是,有一个例外:由列表理解的第一个for子句迭代的序列是在内部函数外部计算的。这就是你的A类在Python3中工作的原因。它这样做是为了让生成器可以立即捕获不可迭代的对象(而不是只有在调用next并运行它们的代码时)。

但它不适用于类B的两级理解中的内部for子句。

如果您使用dis模块反汇编一些创建列表理解的函数,就会看到不同之处:

代码语言:javascript
复制
def f(lst):
    return [i for i in lst]

def g(lst):
    return [(i, j) for i in lst for j in lst]

下面是f的反汇编

代码语言:javascript
复制
>>> 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的反汇编,它通过对同一列表迭代两次来生成对:

代码语言:javascript
复制
>>> 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         

这一次,它使用代码对象构建一个闭包,而不是一个基本函数。闭包是具有一些“自由”变量的函数,这些变量引用封闭作用域中的内容。对于g中的<listcomp>函数,这可以很好地工作,因为它的作用域是普通的。但是,当您尝试在类B中使用相同类型的理解时,闭包会失败,因为类不会让它们包含的函数以这种方式进入它们的作用域(正如上面的Foo类所演示的那样)。

值得注意的是,不仅仅是内部序列值导致了这个问题。与BrenBarn在注释中链接到的previous question中一样,如果在列表理解中的其他地方引用类变量,也会遇到同样的问题:

代码语言:javascript
复制
class C:
    num = 5
    products = [i * num for i in range(10)] # raises a NameError about num

但是,在多级列表理解中,内部for (或if)子句只引用前面循环的结果,因此不会出现错误。这是因为这些值不是闭包的一部分,只是<listcomp>函数作用域内的局部变量。

代码语言:javascript
复制
class D:
    nested = [[1, 2, 3], [4, 5, 6]]
    flattened = [item for inner in nested for item in inner] # works!

就像我说的,类的作用域有点奇怪。

票数 25
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20136955

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档