为什么我在Python类定义中的生成器中得到此NameError?

时间:2015-10-29 15:20:02

标签: python class generator nameerror

在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

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

4 个答案:

答案 0 :(得分:1)

这是因为表达式for i in a具有局部变量范围,而表达式for j in b位于范围内,因此找不到b。<登记/> 实际上,如果你写c = tuple((i, j) for i in a for j in b),它会抛出相同的异常。

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

答案 1 :(得分:0)

我花了很多年的时间进行实验,并且我有一个关于你为什么会遇到这个错误的理论。我不确定,但这确实解释了为什么它适用于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.

总结:

  1. 首先进行循环
  2. 转到元组功能
  3. 执行剩余的循环
  4. 如果第2或第3个循环中的变量在Foo类
  5. 中,则会发生错误

答案 2 :(得分:0)

在我看来,错误的产生是因为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 :(得分:0)

针对您的具体案例的解决方案是使用itertools

namespace Tests\Unit\Services;

use App\User;
use App\Api\LocalStorageService;

public function testCreateFolder()
{
    $user = factory(User::class)->make();

    $localStorageService = $this->createMock(LocalStorageService::class);

    $localStorageService->method('createFolder')
            ->willReturn(array('folder_id' => 'random_id', 'folder_path' => ("/Users/" . $user->email)));
}

看似意外的行为的解释是双重的:

  1. 只有{strong>根类范围才能访问d = tuple(itertools.product(a, b)) 等裸类属性。见pep 227

      

    无法访问类范围中的名称。名称在最里面的封闭函数范围内解析。如果类定义出现在嵌套作用域链中,则解析过程将跳过类定义。

  2. 生成器中的嵌套循环不像你期望的那样起作用。第一个循环实际上是最外层,第二个循环最里面。来自python docs

      

    后续条款无法立即评估,因为它们可能取决于之前的for循环。例如:(x * y表示范围(10),y表示条形(x))。

  3. 第二点可以用添加的换行符说明。

    b

    这意味着d = tuple((i,j) for i in a for j in b) 实际上是从内部循环(嵌套范围)引用的,因此抛出了b。然而,在第一个发生器中,引用位于外部,它可以正常工作。