Zakres klas zagnieżdżonych?

116

Próbuję zrozumieć zakres w zagnieżdżonych klasach w Pythonie. Oto mój przykładowy kod:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

Tworzenie klasy nie kończy się i pojawia się błąd:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Próbowanie inner_var = Outerclass.outer_varnie działa. Dostaję:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

Próbuję uzyskać dostęp do statycznej outer_varz InnerClass.

Czy jest na to sposób?

martineau
źródło

Odpowiedzi:

105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

To nie jest to samo, co podobne rzeczy działają w innych językach i używa wyszukiwania globalnego zamiast ograniczania dostępu do outer_var. (Jeśli zmienisz obiekt, do którego Outerjest przypisana nazwa , kod użyje tego obiektu przy następnym wykonaniu).

Jeśli zamiast tego chcesz, aby wszystkie Innerobiekty miały odniesienie do atrybutu, Outerponieważ outer_varjest to naprawdę atrybut instancji:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Zauważ, że zagnieżdżanie klas jest dość rzadkie w Pythonie i nie oznacza automatycznie żadnego specjalnego związku między klasami. Lepiej nie zagnieżdżać się. (Nadal można ustawić atrybut class na Outercelu Inner, jeśli chcesz).

martineau
źródło
2
Warto dodać, z którymi wersjami Pythona będzie działać Twoja odpowiedź.
AJ.
1
Napisałem to z myślą o 2.6 / 2.x, ale patrząc na to, nie widzę nic, co nie działałoby tak samo w 3.x.
Nie do końca rozumiem, co masz na myśli w tej części: „(Jeśli zmienisz obiekt, do którego jest przypisana nazwa Outer, ten kod użyje tego obiektu przy następnym uruchomieniu).” Czy możesz mi pomóc zrozumieć?
batbrat
1
@batbrat oznacza, że ​​odniesienie do Outerjest wyszukiwane na nowo za każdym razem, gdy to robisz Inner.inner_var. Więc jeśli ponownie powiążesz nazwę Outerz nowym obiektem, Inner.inner_varzacznie zwracać ten nowy obiekt.
Felipe
45

Myślę, że możesz po prostu zrobić:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Przyczyną napotkanego problemu jest:

Blok to fragment tekstu programu w języku Python, który jest wykonywany jako jednostka. Oto bloki: moduł, treść funkcji i definicja klasy.
(...)
Zakres określa widoczność nazwy w bloku.
(...)
Zakres nazw zdefiniowanych w bloku klasy jest ograniczony do bloku klasy; nie rozciąga się na bloki kodu metod - dotyczy to również wyrażeń generatora, ponieważ są one implementowane przy użyciu zakresu funkcji. Oznacza to, że następujące działania zakończą się niepowodzeniem:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

Powyższe oznacza, że:
ciało funkcji jest blokiem kodu, a metoda jest funkcją, wtedy nazwy zdefiniowane poza treścią funkcji w definicji klasy nie rozciągają się na treść funkcji.

Parafrazując to dla twojego przypadku:
definicja klasy jest blokiem kodu, wtedy nazwy zdefiniowane poza definicją klasy wewnętrznej obecnej w definicji klasy zewnętrznej nie rozciągają się na definicję klasy wewnętrznej.

eyquem
źródło
2
Niesamowity. Twój przykład zawodzi, twierdząc, że „nazwa globalna„ a ”nie jest zdefiniowana”. Jednak podstawienie rozumienia listy z [a + i for i in range(10)]powodzeniem wiąże Ab z oczekiwaną listą [42..51].
George,
6
@George Zauważ, że przykład z klasą A nie jest mój, pochodzi z oficjalnego dokumentu Pythona, do którego podałem link. Ten przykład kończy się niepowodzeniem i właśnie ten błąd powinien być pokazany w tym przykładzie. W rzeczywistości list(a + i for i in range(10))to list((a + i for i in range(10)))znaczy list(a_generator). Mówią, że generator jest zaimplementowany w podobnym zakresie niż zakres funkcji.
eyquem
@George Dla mnie oznacza to, że funkcje działają inaczej w zależności od tego, czy znajdują się w module, czy w klasie. W pierwszym przypadku funkcja wychodzi na zewnątrz, aby znaleźć obiekt powiązany z wolnym identyfikatorem. W drugim przypadku funkcja, czyli metoda, nie wychodzi poza swoje ciało. Funkcje w module i metody w klasie są w rzeczywistości dwoma rodzajami obiektów. Metody to nie tylko funkcje w klasie. To mój pomysł.
eyquem
5
@George: FWIW, ani list(...)wywołanie, ani rozumienie nie działają w Pythonie 3. Dokumentacja dla Py3 jest również nieco inna, co odzwierciedla to. Teraz mówi: „ Zakres nazw zdefiniowanych w bloku klasy jest ograniczony do bloku klasy, ale nie obejmuje bloki kodu metod - obejmuje to listowych . I wyrażeń generatora, ponieważ są one zaimplementowane przy użyciu zakresu funkcji ” (Kopalnia nacisk ).
martineau
Jestem ciekaw, dlaczego lista (Aa + i for i in range (10)) również nie działa, gdzie naprawiłem a przez Aa Myślę, że A może być nazwą globalną.
gaoxinge
20

Lepiej by było, gdybyś po prostu nie używał klas zagnieżdżonych. Jeśli musisz zagnieżdżać, spróbuj tego:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Lub zadeklaruj obie klasy przed ich zagnieżdżeniem:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Po tym możesz, del InnerClassjeśli zajdzie taka potrzeba).

Jason Orendorff
źródło
3

Najłatwiejsze rozwiązanie:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

Wymaga od ciebie wyraźnego, ale nie wymaga dużego wysiłku.

Cristian Garcia
źródło
NameError: name „OuterClass” nie jest zdefiniowana - -1
Mr_and_Mrs_D
3
Chodzi o to, aby uzyskać do niego dostęp z zakresu klasy Outer - plus podobny błąd wystąpi, jeśli wywołasz to z zakresu statycznego wewnątrz Outer. Proponuję usunąć ten post
Mr_and_Mrs_D
1

W Pythonie zmienne obiekty są przekazywane jako referencje, więc możesz przekazać odwołanie do klasy zewnętrznej do klasy wewnętrznej.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)
T. Alpers
źródło
2
Zwróć uwagę, że masz tutaj cykl odniesienia iw niektórych scenariuszach instancja tej klasy nie zostanie zwolniona. Jeden przykład, z cPythonem, jeśli masz zdefiniowaną __del__ metodę, garbage collector nie będzie w stanie obsłużyć cyklu referencyjnego, a obiekty wejdą do gc.garbage. Powyższy kod, taki jak jest, nie jest jednak problematyczny. Aby sobie z tym poradzić, użyj słabego odniesienia . Możesz przeczytać dokumentację o słabymref (2.7) lub słabymref (3.5)
guyarad
0

Wszystkie wyjaśnienia można znaleźć w dokumentacji Pythona The Python Tutorial

Twój pierwszy błąd <type 'exceptions.NameError'>: name 'outer_var' is not defined. Wyjaśnienie jest następujące:

Nie ma żadnego skrótu do odwoływania się do atrybutów danych (lub innych metod!) Z poziomu metod. Uważam, że w rzeczywistości zwiększa to czytelność metod: nie ma szans na pomylenie zmiennych lokalnych i zmiennych instancji podczas przeglądania metody.

cytowane z The Python Tutorial 9.4

Drugi błąd <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Kiedy definicja klasy jest opuszczana normalnie (przez koniec), tworzony jest obiekt klasy.

cytat z The Python Tutorial 9.3.1

Więc kiedy próbujesz inner_var = Outerclass.outer_var, Quterclassplik nie został jeszcze utworzony, dlategoname 'OuterClass' is not defined

Bardziej szczegółowe, ale żmudne wyjaśnienie pierwszego błędu:

Chociaż klasy mają dostęp do obejmujących zakresy funkcji, jednak nie działają one jako otaczające zakresy kodu zagnieżdżonego w klasie: Python wyszukuje otaczające funkcje pod kątem nazw, do których istnieją odniesienia, ale nigdy nie obejmują klas obejmujących. Oznacza to, że klasa jest zakresem lokalnym i ma dostęp do otaczających zakresów lokalnych, ale nie służy jako obejmujący zakres lokalny do dalszego zagnieżdżonego kodu.

cytowane z Learning.Python (5th) .Mark.Lutz

Andy Guo
źródło