当前位置: 首页 > 面试题库 >

嵌套列表理解范围

曾嘉瑞
2023-03-14
问题内容

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

example.py:

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]

当我在python 2下运行它时,它工作正常,但是在python 3下,我得到了NameErrorfor类B(但不是class A):

$ 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

为什么只有class会B引发a NameError,为什么只在Python 3下?


问题答案:

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

在Python 2中,迭代变量(i以及j您的示例中)从列表理解中泄漏出来,并将包含在外部范围内。这是因为它们是在Python
2的设计早期开发的,并且它们基于显式循环。作为一个意外的示例,请检查Python 2中没有出现错误的B.i和的值B.j

在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调用它们并运行其代码时捕获)。

但这对于forclass的二级理解中的内部子句不起作用B

如果您反汇编一些使用dis模块创建列表推导的函数,则可以看到区别:

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(或if)子句仅引用前面的循环的结果。这是因为这些值不是闭包的一部分,只是<listcomp>函数范围内的局部变量。

class D:
    nested = [[1, 2, 3], [4, 5, 6]]
    flattened = [item for inner in nested for item in inner] # works!

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



 类似资料:
  • 问题内容: 我试图在一个内部列表中使用外部列表理解的值: 但是不幸的是,这会引发NameError,因为名称是未知的(尽管外部列表理解指定了该名称)。 这是Python的局限性(尝试过2.7.3和3.2.3)还是有充分的理由解释为什么它不起作用? 是否有计划摆脱限制? 是否有解决方法(可能我没有弄清楚一些不同的语法)来实现我想要的? 问题答案: 您在谈论列表 推导 ,而不是生成器表达式。 您需要交

  • 问题内容: 我想了解嵌套列表的理解。下面,我列出了一个列表理解表达式及其for循环等效项。 我不知道我的理解是否正确。 例如, 相当于 如果可以概括一下,我猜 表格可以翻译为以下内容。(我希望我是对的) 对于更简单的情况, 等于 而, 等于 我问了一个类似的问题,即用于复杂列表理解 的循环表达式的等效项。那里给出的答案是在理解了表单内部的内容之后重构了表单。 我想知道它是如何系统地工作的,因此我可

  • 问题内容: 我有这个嵌套列表: 现在,我要做的是将列表中的每个元素转换为float。我的解决方案是这样的: 但这可以使用嵌套列表理解来完成吗? 我所做的是: 但是结果是一堆100的总数为2400。 任何解决方案,解释将不胜感激。谢谢! 问题答案: 这是使用嵌套列表理解的方法: 这将为你提供一个列表列表,与你开始时的列表类似,只是使用浮点数而不是字符串。如果你想要一个固定列表,则可以使用。

  • 问题内容: 我了解简单列表理解的工作原理,例如: 而且我也了解嵌套列表的综合工作原理: 所以,当我尝试这样做 我期望这样: 但是我得到了: 所以我有更好的方法解决问题,这给了我我想要的 但是我不明白在第一个代码中返回9个元素 问题答案: 它有9个数字的原因是因为python对待 类似于 即,它是一个嵌套循环

  • 问题内容: 我对此毫无疑问: 我以为是全部,但是后来我发现了以下片段: 这使。问题是我很难理解中的语法,有人可以解释它的工作原理吗? 问题答案: 难以理解的“嵌套”理解。循环以与理解相同的顺序展开。 这样有助于你进行思考。

  • 问题内容: 谁能告诉我如何在嵌套列表中调用索引? 通常我只写: 但是如果我有一个带有嵌套列表的列表,如下所示: 我想分别浏览每个索引? 问题答案: 如果您确实需要索引,则可以按照内部列表再次执行以下操作: 但是遍历列表本身是更pythonic的: 如果您确实需要索引,也可以使用: