2016-04-06 8 views
1

Ich stieß auf ein Problem, das ich nicht verstehe, und es ist ziemlich kompliziert, also gebe ich mein Bestes, um es hier zu brechen. Bitte sehen Sie sich die folgende Implementierung an. Meine Frage ist: Warum ruft die Vaterklasse die __getitem__ Methode ihres Kindes an, anstatt ihr eigenes __getitem__ zu nennen?Python: aufeinanderfolgende __getitem __() Aufrufe in geerbten Klassen?

class Father(object): 
    ''' Father class ''' 
    def __init__(self,name,gender): 
     print('call __init__ Father')  
     self._name = name 
     # trying to call Father's __getitem__ here 
     self._gender=self[gender] 

    def __getitem__(self,key): 
     print('call __getitem__ Father') 
     return key 

class Child(Father): 
    ''' inherited from Father class ''' 
    def __init__(self, name, gender, age=None): 
     print('call __init__ Child') 
     super(self.__class__, self).__init__(name, gender) 
     self._age = age 

    def __getitem__(self, key): 
     print('call __getitem__ Child') 
     if self._name == 'Marie' and self._age == None: 
      print('I am not sure, how old I am') 
     return super(self.__class__, self).__getitem__(key) 

one=Child('Klaus','male','12')   
other=Child('Marie','female') 

die Fehlermeldung, die auftreten wird, ist:

call __init__ Child 
call __init__ Father 
call __getitem__ Child 
call __getitem__ Father 
call __init__ Child 
call __init__ Father 
call __getitem__ Child 
Traceback (most recent call last): 

    File "<ipython-input-58-2d97b09f6e25>", line 1, in <module> 
    runfile('F:/python-workspace/more-fancy-getitem-fix/test.py', wdir='F:/python-workspace/more-fancy-getitem-fix') 

    File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 601, in runfile 
    execfile(filename, namespace) 

    File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 66, in execfile 
    exec(compile(scripttext, filename, 'exec'), glob, loc) 

    File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 26, in <module> 
    other=Child('Marie','female') 

    File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 16, in __init__ 
    super(self.__class__, self).__init__(name, gender) 

    File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 6, in __init__ 
    self._gender=self[gender] 

    File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 21, in __getitem__ 
    if self._name == 'Marie' and self._age == None: 

AttributeError: 'Child' object has no attribute '_age' 

Ich kann dieses Verhalten nicht erwarten. Ich habe versucht, Kind von Vater zu erben, indem ich Funktionalität hinzufügte. Daher ist die obige Syntax notwendig und macht in meinem komplexeren Code Sinn. Das erste Beispiel von einem = Kind ('Klaus', 'männlich', '12') läuft glatt und man kann schon sehen, dass im Konstruktor des Vaters das __getitem__ des Kindes genannt wird. Im zweiten Beispiel von anderen = Kind ('Marie', 'weiblich') kann man sehen, warum dieser umgekehrte Anruf mich beunruhigt. Hier wird der Code nicht ausgeführt, bis self._age = age definiert ist.

Appart von Vorschlägen, wie dieser Anruf umgekehrt nützlich sein könnte und für was es beabsichtigt war, wäre ich sehr dankbar für Lösungen, wie man die eigene __getitem__ Methode in Vater explizit nennt. Nur schreiben

macht leider nicht den Trick und produziert den gleichen Fehler.

+2

Sie sollten 'super (self .__ class__, self)' nie verwenden. Das wird für immer rekursieren, wenn ich eine "Klasse GrandChild (Child): pass" erstelle – Eric

+0

Klarstellung @ Erics Punkt: In Python 3 würden Sie einfach 'super()' verwenden, was einfach und korrekt ist. In Python 2 müssen Sie die Klasse aufrufen, in der die Methode explizit anhand des Namens definiert ist, z. in deinem Code, Super (Child, self), so kann in Vererbungsfällen die "Super" -Magie verfolgen, welche Überladungen bereits aufgerufen wurden. 'self .__ class__' wird sich ändern, basierend auf welcher Ebene in der Vererbungshierarchie, in der Sie sich befinden, und würde nur für den ersten 'Super'-Aufruf korrekt sein; danach würde es die Klasse des Kindes passieren, nicht die Eltern, wie es sollte. – ShadowRanger

+0

Danke für diesen Hinweis, ich dachte, dass diese Syntax allgemeiner ist, und ich würde hier nicht umbenennen müssen, falls ich den Klassennamen der Väter ändern möchte. Hab nicht umgekehrt gedacht. – c3po

Antwort

2

zu beheben, tauschen nur die Reihenfolge der Initialisierung im Child__init__ so die Child von Child.__getitem__ erforderliche Attribut initialisiert wird, bevor es gebraucht wird:

def __init__(self, name, gender, age=None): 
    print('call __init__ Child') 
    self._age = age 
    super(Child, self).__init__(name, gender) 

Dieses Update ist einzigartig in diesem speziellen Szenario, aber es ist in der Regel auch die richtige Lösung; Im Allgemeinen besteht die einzige Aufgabe für Elternklassen darin, zu versuchen, die Unterklasse nicht unnötig zu unterbrechen, aber es kann nicht erwartet werden, dass sie eine Kindklasse plant, um neue Klasseninvarianten zu erstellen und sie im Voraus anzupassen. Von der untergeordneten Klasse wird erwartet, dass sie über vollständige Kenntnisse des übergeordneten Elements verfügt, und es liegt in der Verantwortung der untergeordneten Klasse sicherzustellen, dass sie in diesem Fall keine Verhaltensweisen des übergeordneten Elements unterbricht, indem sichergestellt wird, dass alle neuen erforderlichen Attribute vor dem übergeordneten Element definiert sind könnte Methoden verwenden, die sie erfordern.

Warum es die __getitem__ des Kindes nennt, funktioniert die Unterklasse standardmäßig (nicht nur in Python, sondern in den meisten OO-Sprachen). Die self empfangen von Father.__init__ von Child.__init__ ist immer noch ein Child Objekt, so sucht nachschlagen zuerst __getitem__ auf Child. Wenn dies nicht der Fall wäre, hätten Sie ein verwirrendes Verhalten, bei dem eine Indexierung unter Child__getitem__ aufgerufen wurde und einige nicht, was es wirklich nicht intuitiv machen würde.

Wenn Sie aus irgendeinem Grund unbedingt nur Father__getitem__ in Father.__init__ verwenden müssen, müssen Sie nur explizit sein, welche Methode Sie aufrufen. Statt self[gender] oder self.__getitem__(gender) (die beide normale Lookup-Prozeduren durchlaufen), tun Sie Father.__getitem__(self, gender), die explizit die Father Version der Methode (und erfordert auch, dass Sie self explizit übergeben, da Sie die ungebundene Methode anstelle einer gebundenen Methode verwenden).

wäre ein weiterer Ansatz, ein Attribut zu definieren, der die Instanz anzeigt, das Kind Überlastung während der Mutter Initialisierer nicht vollständig initialisiert und umgeht so während der Initialisierung der Mutter Methode aufgerufen wird:

def __init__(self, name, gender, age=None): 
    print('call __init__ Child') 
    # Use double underscore prefix to make a private attribute of Child 
    # not directly visible to parent or lower level children so no risk 
    # of being reused by accident 
    self.__in_parent_init = True 
    super(Child, self).__init__(name, gender) 
    self.__in_parent_init = False 
    self._age = age 

def __getitem__(self, key): 
    if self.__in_parent_init: 
     # Don't use Child specific features until Father initialized 
     return super(Child, self).__getitem__(key) 
    print('call __getitem__ Child') 
    if self._name == 'Marie' and self._age == None: 
     print('I am not sure, how old I am') 
    return super(Child, self).__getitem__(key) 
+0

"wechseln Sie einfach die Reihenfolge der Initialisierung in der 'Child'' __________ "- das funktioniert hier, aber versuchen, Dinge durch Neuordnung der Initialisierung in einem komplexeren System zu beheben kann zu einem schrecklichen Netz von subtilen Abhängigkeiten führen. Es ist besser für Konstruktoren, sich niemals auf Unterklassenmethoden oder Unterklasseninitialisierungen zu verlassen. – user2357112

+0

@ user2357112: Einverstanden, dass es spezifisch für diesen Fall ist. Das heißt, Erbschaft ist nie so sauber, wie wir es gerne hätten; wenn 'Vater' von einer externen Codebasis ist und nicht explizit' Vater .__ getitem __ (Selbst, Geschlecht) 'verwendet (weil es nicht vorhersagte, dass ein 'Kind'' __getitem__ 'auf brechende Weise überschreibt), ist das nicht der Fall etwas, das auf der Seite des Vaters fixiert werden kann, und es ist normalerweise sinnvoll, die Klasse der Eltern in der Elternklasse mit Macken zu behandeln, während das Elternteil normalerweise nicht weiß, wer es ist irgendetwas über das Kind). – ShadowRanger

+0

Danke für die gute Antwort! Schalten wäre in der Tat eine Lösung, aber Dinge vermischen, die mir später vielleicht Kopfweh geben. Der ausdrückliche Anruf von Vater war der richtige Hinweis, Danke. – c3po

1
# trying to call Father's __getitem__ here 
self._gender=self[gender] 

Die __getitem__ Methode verwendet wird hier durch die tatsächliche Klasse von self nachgeschlagen. Wenn self eine Instanz von Child ist, wird Child.__getitem__ verwendet. Sie könnten dies Father.__getitem__ verwendet explizit ändern:

self._gender = Father.__getitem__(self, gender) 

wenn Ihr Gesamtkonzept noch irgendwie ein Chaos ist, und es wird wahrscheinlich zu mehr Problemen führen.


Unrelated aber wichtig:

super(self.__class__, self) 

dies nie tun. Dies wird brechen, wenn Sie es in einer Klasse tun, die jemals unterklassifiziert wird. Sie müssen den Klassennamen explizit angeben.

+0

Kurz und präzise, ​​danke! Was ich mit meinem Design erreichen wollte, ist, den Code so effizient wie möglich wiederzuverwenden, indem ich alles, was von anderen Klassen benötigt wird, in Pater einfüge und vererbe. – c3po

Verwandte Themen