Python 从类定义中的列表访问类变量

Python 从类定义中的列表访问类变量,python,python-3.x,scope,list-comprehension,python-internals,Python,Python 3.x,Scope,List Comprehension,Python Internals,如何从类定义中的列表访问其他类变量?以下内容在Python 2中可用,但在Python 3中失败: class Foo: x = 5 y = [x for i in range(1)] Python 3.2给出了以下错误: NameError: global name 'x' is not defined 尝试Foo.x也不起作用。关于如何在Python3中实现这一点,有什么想法吗 一个稍微复杂的激励示例: from collections import namedtuple

如何从类定义中的列表访问其他类变量?以下内容在Python 2中可用,但在Python 3中失败:

class Foo:
    x = 5
    y = [x for i in range(1)]
Python 3.2给出了以下错误:

NameError: global name 'x' is not defined
尝试
Foo.x
也不起作用。关于如何在Python3中实现这一点,有什么想法吗

一个稍微复杂的激励示例:

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]
在本例中,
apply()
本来是一个不错的解决方案,但遗憾的是它已从Python 3中删除。

类范围和列表、集合或字典理解以及生成器表达式不会混合使用

原因;或者,官方的说法 在Python3中,列表理解被赋予了自己适当的作用域(本地名称空间),以防止其局部变量溢出到周围的作用域中(请参阅)。在模块或函数中使用这样的列表理解是很好的,但在类中,作用域有点奇怪

这记录在:

类作用域中的名称不可访问。名称在中解析 最里面的封闭函数作用域。如果一个类定义 在嵌套作用域链中发生时,解析过程将跳过 类定义

以及在:

然后,使用新创建的本地名称空间和原始全局名称空间,在新的执行框架中执行该类的套件(请参见第节)。(通常,套件只包含函数定义。)当类的套件完成执行时,其执行帧被丢弃,但其本地名称空间被保存。然后使用基类的继承列表和属性字典保存的本地名称空间创建类对象

重点矿山;执行框架是临时范围

由于作用域被重新用作类对象上的属性,因此允许将其用作非局部作用域也会导致未定义的行为;例如,如果一个类方法引用
x
作为嵌套范围变量,然后也操作
Foo.x
,会发生什么情况?更重要的是,这对
Foo
的子类意味着什么?Python必须以不同的方式对待类作用域,因为它与函数作用域非常不同

最后,但绝对不是最不重要的一点,执行模型文档中的链接部分明确提到了类作用域:

类块中定义的名称范围仅限于类块;它不扩展到方法的代码块——这包括理解和生成器表达式,因为它们是使用函数作用域实现的。这意味着以下操作将失败:

class A:
     a = 42
     b = list(a + i for i in range(10))
因此,总结一下:您不能从包含在该范围内的函数、列表理解或生成器表达式访问类范围;他们的行为就好像这个范围不存在一样。在Python2中,列表理解是使用快捷方式实现的,但在Python3中,它们有自己的函数范围(一直以来都应该有),因此您的示例中断了。无论Python版本如何,其他理解类型都有自己的作用域,因此类似的集合或dict理解示例将在Python 2中出现

# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(小)例外;或者,为什么一部分仍然有效 理解表达式或生成器表达式有一部分在周围范围内执行,而不管Python版本如何。这将是最外层iterable的表达。在您的示例中,它是
范围(1)

因此,在该表达式中使用
x
不会引发错误:

# Runs fine
y = [i for i in range(x)]
这仅适用于最外层的iterable;如果一个理解有多个
for
子句,则在理解的范围内评估
for
子句的内部
可比性:

# NameError
y = [i for i in range(1) for j in range(x)]
#      ^^^^^^^^^^^^^^^^^ -----------------
#      outer loop        inner, nested loop
import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]
当创建生成器表达式的最外层iterable时抛出错误,或者当最外层iterable结果为不可iterable时,此设计决策是为了在genexp创建时而不是在迭代时抛出错误。理解共享这种行为以保持一致性

从引擎盖下面看;或者,比你想要的更详细 您可以使用来查看这一切。在下面的示例中,我使用的是Python3.3,因为它添加了一个可以清晰地识别我们要检查的代码对象的代码。生成的字节码在其他方面与Python 3.2的功能相同

为了创建一个类,Python基本上采用了构成类主体的整个套件(因此所有内容都比
类:
行缩进了一个级别),并将其作为一个函数执行:

>>> import dis
>>> def foo():
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo)
  2           0 LOAD_BUILD_CLASS     
              1 LOAD_CONST               1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>) 
              4 LOAD_CONST               2 ('Foo') 
              7 MAKE_FUNCTION            0 
             10 LOAD_CONST               2 ('Foo') 
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             16 STORE_FAST               0 (Foo) 

  5          19 LOAD_FAST                0 (Foo) 
             22 RETURN_VALUE         
上面的字节码创建了类主体。该函数被执行,结果的
locals()
名称空间(包含
x
y
)被用于创建类(除非它不工作,因为
x
未定义为全局名称空间)。请注意,在
x
中存储
5
后,它将加载另一个代码对象;这就是列表理解;它被包装在函数对象中,就像类主体一样;创建的函数接受一个位置参数,
范围(1)
iterable用于循环代码,并转换为迭代器。如字节码所示,
范围(1)
在类范围内进行计算

由此可以看出,函数或生成器的代码对象与理解的代码对象之间的唯一区别在于,在执行父代码对象时,后者立即执行;字节码只是动态创建一个函数,并在几个小步骤中执行它

Python2.x在那里使用内联字节码,这里是Python2.7的输出:

  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_CONST               0 (5)
              9 STORE_NAME               2 (x)

  4          12 BUILD_LIST               0
             15 LOAD_NAME                3 (range)
             18 LOAD_CONST               1 (1)
             21 CALL_FUNCTION            1
             24 GET_ITER            
        >>   25 FOR_ITER                12 (to 40)
             28 STORE_NAME               4 (i)
             31 LOAD_NAME                2 (x)
             34 LIST_APPEND              2
             37 JUMP_ABSOLUTE           25
        >>   40 STORE_NAME               5 (y)
             43 LOAD_LOCALS         
             44 RETURN_VALUE        
没有加载代码对象,而是内联运行一个
FOR\u ITER
循环。因此,在Python3.x中,列表生成器被赋予了自己适当的代码对象,这意味着它有自己的作用域

然而,该理解是与p
  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_CONST               0 (5)
              9 STORE_NAME               2 (x)

  4          12 BUILD_LIST               0
             15 LOAD_NAME                3 (range)
             18 LOAD_CONST               1 (1)
             21 CALL_FUNCTION            1
             24 GET_ITER            
        >>   25 FOR_ITER                12 (to 40)
             28 STORE_NAME               4 (i)
             31 LOAD_NAME                2 (x)
             34 LIST_APPEND              2
             37 JUMP_ABSOLUTE           25
        >>   40 STORE_NAME               5 (y)
             43 LOAD_LOCALS         
             44 RETURN_VALUE        
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
  4           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_GLOBAL              0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         
>>> def foo():
...     x = 2
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
  5           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_DEREF               0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         
>>> foo.__code__.co_cellvars               # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars  # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars  # Refers to `x` in foo
('x',)
>>> foo().y
[2]
>>> def spam(x):
...     def eggs():
...         return x
...     return eggs
... 
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
>>> class Foo:
...     x = 5
...     def y(x):
...         return [x for i in range(1)]
...     y = y(x)
... 
>>> Foo.y
[5]
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
def __init__(self):
    self.y = [self.x for i in range(1)]
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])

class StateDatabase:
    db = [State(*args) for args in [
       ('Alabama', 'Montgomery'),
       ('Alaska', 'Juneau'),
       # ...
    ]]
class A:
    x = 4
    y = [x+i for i in range(1)]
class A:
    x = 4
    y = (lambda x=x: [x+i for i in range(1)])()
class Foo:

    # A class-level variable.
    X = 10

    # I can use that variable to define another class-level variable.
    Y = sum((X, X))

    # Works in Python 2, but not 3.
    # In Python 3, list comprehensions were given their own scope.
    try:
        Z1 = sum([X for _ in range(3)])
    except NameError:
        Z1 = None

    # Fails in both.
    # Apparently, generator expressions (that's what the entire argument
    # to sum() is) did have their own scope even in Python 2.
    try:
        Z2 = sum(X for _ in range(3))
    except NameError:
        Z2 = None

    # Workaround: put the computation in lambda or def.
    compute_z3 = lambda val: sum(val for _ in range(3))

    # Then use that function.
    Z3 = compute_z3(X)

    # Also worth noting: here I can refer to XS in the for-part of the
    # generator expression (Z4 works), but I cannot refer to XS in the
    # inner-part of the generator expression (Z5 fails).
    XS = [15, 15, 15, 15]
    Z4 = sum(val for val in XS)
    try:
        Z5 = sum(XS[i] for i in range(len(XS)))
    except NameError:
        Z5 = None

print(Foo.Z1, Foo.Z2, Foo.Z3, Foo.Z4, Foo.Z5)
class Foo:
    x = 5
    y = [x for i in range(1)]
def Foo():
    x = 5
    y = [x for i in range(1)]
import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]
class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]
from collections import namedtuple
import itertools as it

class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for State, args in zip(it.repeat(State), [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ])]