Почему я получаю этот 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
Почему я получаю эту ошибку? И почему я не получаю эту ошибку в предыдущей строке?
Ответы
Ответ 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
. Однако в первом генераторе ссылка находится во внешнем, который отлично работает.