Защо получавам тази 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

Защо получавам тази грешка? И защо не получавам тази грешка на предишния ред?


person Wangnick    schedule 29.10.2015    source източник
comment
Тъй като генераторните изрази и дефинициите на класове са техен собствен обхват   -  person jonrsharpe    schedule 29.10.2015
comment
Но ако и двете са в техния собствен обхват, защо, по дяволите, достъпът до b в предишния ред (c=...) е успешен?   -  person Wangnick    schedule 29.10.2015
comment
В първия пример 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)) ще работи и в двата случая.   -  person jonrsharpe    schedule 29.10.2015
comment
Така че незабавната оценка не се случва в рамките на обхвата на генераторния израз, а по-скоро в обхвата около дефиницията на генераторния израз. Това умишлено и посочено ли е някъде в документите на Python?   -  person Wangnick    schedule 30.10.2015
comment
Добре, добавих коментар към bugs.python.org/issue11796, за да се направи това изрично в документа на Python. Освен това все още вярвам, че тук има аспекти, които не са разгледани в дублиращата се тема.   -  person Wangnick    schedule 30.10.2015
comment
Тогава, моля, редактирайте, за да изясните какво сте научили от текущия дубликат и на какво все още трябва да отговорите и аз ще го отворя отново.   -  person jonrsharpe    schedule 30.10.2015


Отговори (4)


Това е така, защото изразът for i in a има обхват на локална променлива, а изразът for j in b е вътре в обхвата, следователно не е намерен b.
Всъщност, ако напишете c = tuple((i, j) for i in a for j in b), той ще хвърли същото изключение .

Решението е поставено b в обхвата на дефиницията на клас (както вече направихте) и го препратете от self.b.

person Hou Lu    schedule 20.09.2017

Прекарах векове в експерименти и имам теория защо получавате тази грешка. Не съм сигурен, но това обяснява защо работи за 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.

Накратко:

  1. прави първо for цикъл
  2. преминава към кортежна функция
  3. прави останалите цикли
  4. грешката възниква, ако променлива във 2-ри или 3-ти цикъл е в клас Foo
person Tom Fuller    schedule 02.09.2016

Според мен грешката възниква, защото 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())
person mdpt    schedule 20.09.2017

Решението за вашия конкретен случай е да използвате itertools:

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

Обяснението за привидно неочакваното поведение е две:

  1. #P3#
    #P4#
  2. #P5#
    #P6#

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

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

Това означава, че b всъщност се препраща от вътрешния цикъл (вложен обхват) и по този начин се хвърля NameError. В първия генератор обаче препратката е във външния, който работи добре.

person wihlke    schedule 15.01.2018