为啥我会在 Python 类定义中的生成器中得到这个 NameError?

Posted

技术标签:

【中文标题】为啥我会在 Python 类定义中的生成器中得到这个 NameError?【英文标题】:Why do I get this NameError in a generator within a Python class definition?为什么我会在 Python 类定义中的生成器中得到这个 NameError? 【发布时间】:2016-01-29 19:50:49 【问题描述】:

在 Python 3.5.0 中,此代码:

a = (1,2)
class Foo(object):
    b = (3,4)
    c = tuple((i,j) for j in b for i in a)
    d = tuple((i,j) for i in a for j in b)

产生:

Traceback (most recent call last):
  File "genexprtest.py", line 2, in <module>
    class Foo(object):
  File "genexprtest.py", line 5, in Foo
    d = tuple((i,j) for i in a for j in b)
  File "genexprtest.py", line 5, in <genexpr>
    d = tuple((i,j) for i in a for j in b)
NameError: name 'b' is not defined

为什么会出现此错误?为什么我在上一行没有得到这个错误?

【问题讨论】:

因为生成器表达式和类定义都是它们自己的作用域 但是如果它们都在自己的范围内,为什么在上一行 (c=...) 中对 b 的访问会成功? 在第一个示例中,b 在最外层的 for 表达式中进行了迭代,该表达式立即被求值 - 参见例如python.org/dev/peps/pep-0289/#early-binding-versus-late-binding 的基本原理。同样,如果您将文档中的示例更改为 b = list(i for i in range(a)),它可以正常工作,d = tuple((i,j) for i, j in itertools.product(b, a)) 也可以使用任何一种方式。 因此立即评估不会发生在生成器表达式范围内,而是发生在生成器表达式定义周围的范围内。这是故意的并在 Python 文档中的某处指定? "...the leftmost for clause is immediately evaluated... Subsequent for clauses cannot be evaluated immediately since they may depend on the previous for loop" 【参考方案1】:

我花了很长时间进行实验,并且对您为什么会收到此错误有一个理论。我不确定,但这确实解释了为什么它适用于c 而不适用于d。希望对你有帮助,如果你不同意,请发表评论:)

def Tuple(this):
    print(a) # this always works
    try:
        print(b) # this always gives an error
    except NameError:
        print("...b is not defined")
    try:
        return tuple(this) # this only gives an error for d and e
    except NameError:
        print("...couldn't make it a tuple")


a = (1,2)     
class Foo(object):
    b = (3,4)
    c = Tuple((i,j) for j in b for i in a)
    d = Tuple((i,j) for i in a for j in b)
    e = Tuple((i,j,k) for i in a for j in b for k in (5, 6))
    f = Tuple((i,j,k) for j in b for i in (5, 6) for k in a)

    print("\nc:", c,"\nd:", d,"\ne:", e,"\nf:", f)

发生了什么: 每次我调用 Tuple() 函数时,b 都没有定义,但 a 总是被定义。这解释了为什么您会收到 de 的错误,但它不能解释为什么 cf 工作,即使 b 是“未定义”

我的理论: 第一个for 循环是在整个事物转换为元组之前计算的。例如,如果你尝试这样做:Tuple((a, b, c) for a in loop1, for b in loop2 for c in loop3),在 Foo 类中它会首先计算 for a in loop1,然后它会移动到 foo 并计算循环 2 和 3。

总结:

    首先执行 for 循环 移至元组函数 做剩余的循环 如果第二个或第三个循环中的变量在类 Foo 中,则会发生错误

【讨论】:

【参考方案2】:

在我看来,错误的出现是因为b 被定义为类变量。要正确使用它,您需要这样对待它(self.b)。 另外,你应该使用构造函数:

a = (1, 2)

class Foo(object):
    def __init__(self):
        self.b = (3, 4)
        self.c = tuple((i, j) for j in self.b for i in a)
        self.d = tuple((i, j) for i in a for j in self.b)

这是一个更清晰的代码。它的行为正常。希望对您有所帮助。

编辑:如果您不想使用__init__,也有可能使用方法获得cd

a = (1, 2)

class Foo(object):
    b = (3, 4)

    def get_c(self):
        return tuple((i, j) for j in self.b for i in a)

    def get_d(self):
        return tuple((i, j) for i in a for j in self.b)

这也很好用。 您可以像这样尝试这两种实现:

inst = Foo()
# 1st one
print(inst.c)
print(inst.d)
# 2nd one
print(inst.get_c())
print(inst.get_d())

【讨论】:

【参考方案3】:

这是因为表达式for i in a有一个局部变量作用域,而表达式for j in b在作用域内,因此找不到b。 实际上,如果你写c = tuple((i, j) for i in a for j in b),它会抛出同样的异常。

解决方案将b 放入类定义的范围内(正如您已经做过的那样)并通过self.b 引用它。

【讨论】:

【参考方案4】:

针对您的具体情况的解决方案是使用itertools:

d = tuple(itertools.product(a, b))

对看似意外的行为的解释有两个方面:

    b 等裸类属性只能在 根类范围 中访问。见pep 227:

    类范围内的名称不可访问。名称在最里面的封闭函数范围内解析。如果类定义出现在嵌套范围链中,则解析过程会跳过类定义。

    生成器中的嵌套循环不会像您预期的那样运行。第一个循环实际上是最外面的,第二个是最里面的。来自pythondocs:

    不能立即评估后续的 for 子句,因为它们可能依赖于前一个 for 循环。例如:(x*y for x in range(10) for y in bar(x))。

可以通过添加换行符来说明第二点。

d = tuple((i,j) 
    for i in a
        for j in b)

这意味着b 实际上是从内部循环(嵌套范围)引用的,因此会抛出NameError。然而,在第一个生成器中,引用位于外部生成器中,可以正常工作。

【讨论】:

以上是关于为啥我会在 Python 类定义中的生成器中得到这个 NameError?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我会得到:非法尝试在不同上下文中的对象之间建立关系...

为啥我会得到不同形状的张量错误?

基于它们在python中的合并顺序的层次聚类标签

python中的多线程为啥会报错?

Python smtplib:为啥连接被拒绝?

当我尝试将数据保存在 txt (java) 中时,为啥我会得到这些符号