如何从类定义中的列表理解中访问其他类变量?以下代码在python2中有效,但在python3中无效:

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中被删除了。


当前回答

我花了很多时间来理解为什么这是一个功能,而不是一个bug。

考虑下面的简单代码:

a = 5
def myfunc():
    print(a)

由于myfunc()中没有定义“a”,因此范围将扩大,代码将执行。

现在考虑类中的相同代码。它不能工作,因为这会完全扰乱对类实例中的数据的访问。你永远不会知道,你访问的是基类中的变量还是实例中的变量。

列表理解只是相同效果的一个子案例。

其他回答

可以使用for循环:

class A:
    x=5
##Won't work:
##    y=[i for i in range(101) if i%x==0]
    y=[]
    for i in range(101):
        if i%x==0:
            y.append(i)

请纠正我,我没有错…

公认的答案提供了很好的信息,但这里似乎还有一些其他问题——列表理解和生成器表达式之间的差异。我玩了一个演示:

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)

这是Python中的一个bug。推导式被宣传为等同于for循环,但在类中并非如此。至少在Python 3.6.6之前,在类中使用的理解型中,只有一个来自理解型外部的变量可以在理解型内部访问,并且它必须用作最外层的迭代器。在函数中,此范围限制不适用。

为了说明为什么这是一个错误,让我们回到最初的示例。这个操作失败:

class Foo:
    x = 5
    y = [x for i in range(1)]

但这是可行的:

def Foo():
    x = 5
    y = [x for i in range(1)]

在参考指南的本节末尾说明了限制。

我花了很多时间来理解为什么这是一个功能,而不是一个bug。

考虑下面的简单代码:

a = 5
def myfunc():
    print(a)

由于myfunc()中没有定义“a”,因此范围将扩大,代码将执行。

现在考虑类中的相同代码。它不能工作,因为这会完全扰乱对类实例中的数据的访问。你永远不会知道,你访问的是基类中的变量还是实例中的变量。

列表理解只是相同效果的一个子案例。

由于最外层的迭代器是在周围范围内计算的,我们可以将zip与itertools一起使用。重复将依赖项转移到理解范围:

import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]

还可以在推导式中使用嵌套for循环,并在最外层的可迭代对象中包含依赖项:

class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]

对于OP的具体例子:

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'],
        # ...
    ])]