Подтвердить что ты не робот

Почему я получаю этот NameError в генераторе в определении класса Python?

В 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

Почему я получаю эту ошибку? И почему я не получаю эту ошибку в предыдущей строке?

4b9b3361

Ответ 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.

Вкратце:

  • делает сначала цикл
  • перемещается в кортеж функции
  • Остальные петли
  • ошибка возникает, если переменная во втором или третьем цикле находится в классе 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)

Это более четкий код. И он ведет себя правильно. Надеюсь, что это поможет.

EDIT: если вы не хотите использовать __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:

    Имена в классе не доступны. Имена разрешаются в самой внутренней области приложения. Если определение класса происходит в цепочке вложенных областей, процесс разрешения пропускает определения классов.

  • Вложенные циклы в генераторах не работают, как вы могли ожидать. Первый цикл на самом деле является самым внешним, а второй - самым внутренним. Из python docs:

    Последующие предложения не могут быть оценены немедленно, так как они могут зависеть от предыдущего цикла for. Например: (x * y для x в диапазоне (10) для y в bar (x)).

Вторая точка может быть проиллюстрирована добавленными разрывами строк.

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

Это означает, что b на самом деле ссылается на внутренний цикл (вложенную область) и, таким образом, бросается NameError. Однако в первом генераторе ссылка находится во внешнем, который отлично работает.