Umfang verschachtelter Klassen?


116

Ich versuche, den Umfang in verschachtelten Klassen in Python zu verstehen. Hier ist mein Beispielcode:

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

Die Erstellung der Klasse ist nicht abgeschlossen und ich erhalte den Fehler:

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

Der Versuch inner_var = Outerclass.outer_varfunktioniert nicht. Ich bekomme:

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

Ich versuche , die statische zuzugreifen outer_varaus InnerClass.

Gibt es eine Möglichkeit, dies zu tun?

Antworten:


105
class Outer(object):
    outer_var = 1

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

Dies ist nicht ganz dasselbe wie ähnliche Dinge in anderen Sprachen und verwendet die globale Suche, anstatt den Zugriff auf zu beschränken outer_var. (Wenn Sie ändern, an welches Objekt der Name Outergebunden ist, verwendet dieser Code dieses Objekt bei der nächsten Ausführung.)

Wenn Sie stattdessen möchten, dass alle InnerObjekte einen Verweis auf ein Outerweil haben, outer_varist dies wirklich ein Instanzattribut:

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

Beachten Sie, dass das Verschachteln von Klassen in Python etwas ungewöhnlich ist und nicht automatisch eine spezielle Beziehung zwischen den Klassen impliziert. Du bist besser dran, nicht zu nisten. (Sie können immer noch ein Klasse - Attribut auf , Outerum Inner, wenn Sie möchten.)


2
Es kann hilfreich sein, hinzuzufügen, mit welcher Version (en) von Python Ihre Antwort funktioniert.
AJ.

1
Ich habe dies mit Blick auf 2.6 / 2.x geschrieben, aber wenn ich es mir anschaue, sehe ich nichts, was in 3.x nicht gleich funktionieren würde.

Ich verstehe nicht ganz, was Sie in diesem Teil meinen. "(Wenn Sie ändern, an welches Objekt der Name Outer gebunden ist, verwendet dieser Code dieses Objekt bei der nächsten Ausführung.)" Können Sie mir bitte helfen, es zu verstehen?
Batbrat

1
@batbrat bedeutet, dass der Verweis auf Outerjedes Mal neu nachgeschlagen wird Inner.inner_var. Wenn Sie den Namen Outeran ein neues Objekt binden , Inner.inner_varwird dieses neue Objekt zurückgegeben.
Felipe

45

Ich denke, Sie können einfach tun:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Das Problem, auf das Sie gestoßen sind, ist folgendes:

Ein Block ist ein Teil des Python-Programmtextes, der als Einheit ausgeführt wird. Das Folgende sind Blöcke: ein Modul, ein Funktionskörper und eine Klassendefinition.
(...)
Ein Bereich definiert die Sichtbarkeit eines Namens innerhalb eines Blocks.
(...)
Der Umfang der in einem Klassenblock definierten Namen ist auf den Klassenblock beschränkt. Es erstreckt sich nicht auf die Codeblöcke von Methoden - dies schließt Generatorausdrücke ein, da diese mithilfe eines Funktionsumfangs implementiert werden. Dies bedeutet, dass Folgendes fehlschlägt:

   class A:  

       a = 42  

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

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

Das Obige bedeutet:
Ein Funktionskörper ist ein Codeblock und eine Methode ist eine Funktion. Namen, die aus dem in einer Klassendefinition vorhandenen Funktionskörper definiert wurden, erstrecken sich nicht auf den Funktionskörper.

Um dies für Ihren Fall zu umschreiben:
Eine Klassendefinition ist ein Codeblock. Namen, die aus der in einer äußeren Klassendefinition vorhandenen inneren Klassendefinition definiert wurden, erstrecken sich nicht auf die innere Klassendefinition.


2
Tolle. Ihr Beispiel schlägt fehl und behauptet, "globaler Name 'a' ist nicht definiert". Das Ersetzen eines Listenverständnisses [a + i for i in range(10)]bindet Ab jedoch erfolgreich an die erwartete Liste [42..51].
George

6
@ George Beachten Sie, dass das Beispiel mit Klasse A nicht von mir stammt, sondern aus dem offiziellen Python-Dokument, dessen Link ich gegeben habe. Dieses Beispiel schlägt fehl und dieser Fehler soll in diesem Beispiel gezeigt werden. In der Tat list(a + i for i in range(10))ist list((a + i for i in range(10)))das zu sagen list(a_generator). Sie sagen, dass ein Generator mit einem ähnlichen Umfang wie der Funktionsumfang implementiert ist.
Eyquem

@George Für mich bedeutet das, dass Funktionen unterschiedlich funktionieren, je nachdem, ob sie sich in einem Modul oder in einer Klasse befinden. Im ersten Fall geht eine Funktion nach draußen, um das Objekt zu finden, das an eine freie Kennung gebunden ist. Im zweiten Fall geht eine Funktion, dh eine Methode, nicht über ihren Körper hinaus. Funktionen in einem Modul und Methoden in einer Klasse sind in Wirklichkeit zwei Arten von Objekten. Methoden sind nicht nur Funktionen in der Klasse. Das ist meine Idee.
Eyquem

5
@ George: FWIW, weder der list(...)Aufruf noch das Verständnis funktionieren in Python 3. Die Dokumentation für Py3 ist auch etwas anders, was dies widerspiegelt. Es heißt jetzt " Der Umfang der in einem Klassenblock definierten Namen ist auf den Klassenblock beschränkt; er erstreckt sich nicht auf die Codeblöcke von Methoden - dies schließt Verständnisse und Generatorausdrücke ein, da sie unter Verwendung eines Funktionsumfangs implementiert werden. " ).
Martineau

Ich bin gespannt, warum die Liste (Aa + i für i in Bereich (10)) auch nicht funktioniert, wo ich a von Aa behoben habe. Ich denke, A ist ein globaler Name.
Gaoxinge

20

Sie sind möglicherweise besser dran, wenn Sie keine verschachtelten Klassen verwenden. Wenn Sie nisten müssen, versuchen Sie Folgendes:

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

Oder deklarieren Sie beide Klassen, bevor Sie sie verschachteln:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Danach können Sie, del InnerClasswenn Sie müssen.)


3

Einfachste Lösung:

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

Es erfordert, dass Sie explizit sind, aber nicht viel Aufwand.


NameError: Name 'OuterClass' ist nicht definiert - -1
Mr_and_Mrs_D

3
Der Punkt ist, über den Bereich der äußeren Klasse darauf zuzugreifen. Außerdem tritt ein ähnlicher Fehler auf, wenn Sie dies über einen statischen Bereich innerhalb von Outer aufrufen. Ich würde vorschlagen, dass Sie diesen Beitrag löschen
Mr_and_Mrs_D

1

In Python werden veränderbare Objekte als Referenz übergeben, sodass Sie eine Referenz der äußeren Klasse an die innere Klasse übergeben können.

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)

2
Bitte beachten Sie, dass Sie hier einen Referenzzyklus haben und in einigen Szenarien die Instanz dieser Klasse nicht freigegeben wird. In einem Beispiel mit cPython kann __del__ der Garbage Collector den Referenzzyklus nicht verarbeiten, wenn Sie eine Methode definiert haben , und die Objekte werden ausgeführt gc.garbage. Der obige Code ist jedoch nicht problematisch. Der Umgang damit ist die Verwendung einer schwachen Referenz . Sie können die Dokumentation über schwache Referenz (2.7) oder schwache Referenz (3.5)
lesen

0

Alle Erklärungen finden Sie in der Python-Dokumentation Das Python-Tutorial

Für Ihren ersten Fehler <type 'exceptions.NameError'>: name 'outer_var' is not defined. Die Erklärung lautet:

Es gibt keine Abkürzung zum Verweisen auf Datenattribute (oder andere Methoden!) Innerhalb von Methoden. Ich finde, dass dies tatsächlich die Lesbarkeit von Methoden erhöht: Es besteht keine Möglichkeit, lokale Variablen und Instanzvariablen zu verwechseln, wenn Sie einen Blick durch eine Methode werfen.

zitiert aus dem Python-Tutorial 9.4

Für Ihren zweiten Fehler <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Wenn eine Klassendefinition normal (über das Ende) belassen wird, wird ein Klassenobjekt erstellt.

zitiert aus dem Python-Tutorial 9.3.1

Wenn Sie es versuchen inner_var = Outerclass.outer_var, wurde das Quterclassnoch nicht erstellt, deshalbname 'OuterClass' is not defined

Eine detailliertere, aber langwierige Erklärung für Ihren ersten Fehler:

Obwohl Klassen Zugriff auf die Bereiche der einschließenden Funktionen haben, fungieren sie nicht als einschließende Bereiche für Code, der in der Klasse verschachtelt ist: Python durchsucht einschließende Funktionen nach referenzierten Namen, jedoch niemals nach einschließenden Klassen. Das heißt, eine Klasse ist ein lokaler Bereich und hat Zugriff auf einschließende lokale Bereiche, dient jedoch nicht als einschließender lokaler Bereich für weiteren verschachtelten Code.

zitiert aus Learning.Python (5.) .Mark.Lutz

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.