为啥我会在 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
总是被定义。这解释了为什么您会收到 d
和 e
的错误,但它不能解释为什么 c
和 f
工作,即使 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__
,也有可能使用方法获得c
和d
:
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?的主要内容,如果未能解决你的问题,请参考以下文章