OO Frage bzgl. super/init/new usw. in Wrapperklasse.

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Hallo alle,

es geht mir hier nur darum, ein korrektes Verständis zur Verwendung von __super__(), __init__(), __new__() usw. zu bekommen.

Ich wollte meine eigene Klasse "MyDict" bauen, von dict erben und dann die Klasse erweitern um bestimmte Funktionen.

.clear_values() soll nur die values "nullen", bzw. "none"-en (lies: Nonnen :lol: ).

.flip() soll Key<->Value umkehren und falls das ein Problem gibt, soll die Oberklasse (dict) entscheiden, was passiert (oder Exceptions werfen).

Beispiel, was ich mit .flip() meine:

Code: Alles auswählen

>>> d = {'Germany': 'Berlin', 'Greece': 'Athens', 'France': 'Paris', 'England': 'London'}
>>> d
{'England': 'London', 'France': 'Paris', 'Germany': 'Berlin', 'Greece': 'Athens'}
>>> d = dict((v, k) for k, v in d.items())
>>> d
{'Paris': 'France', 'Berlin': 'Germany', 'London': 'England', 'Athens': 'Greece'}
Hier der Code:

Code: Alles auswählen

class MyDict(dict):

	def __init__(self,*args,**kwargs):
		super(MyDict, self).__init__(*args, **kwargs)

	def clear_values(self):
		self.__init__(super(MyDict, self).fromkeys(self.keys()))

	def flip(self):
		self.__init__(((v, k) for k, v in self.items()))
Folgende Fragen kommen dabei auf:

1. das super(..).__init__(..) habe ich von irgendwo ausm Internet übernommen, verstehe aber nicht, warum das so gut (oder besser) ist.
Da ich keine abweichenden/neuen Attribute definiere, ist mir auch nicht klar, weshalb man hier zwingend eine __init__() angeben müsste.

2. fromkeys() ist eine Klassenfunktion, welche ein neues dict() zurückgibt. Hier verstehe ich nicht, weshalb ich zwingend super(MyDict, self) eingeben muss,
statt einfach nur super(MyDict)... ohne das self Objekt. Wozu braucht er noch das Objeckt self, wenns ne Klassenfunktion ist?
Gebe ich stattdessen super(MyDict).fromkeys(self.keys) an, komm die Fehlermeldung: AttributeError: 'super' object has no attribute 'fromkeys'

3. ich hätte erwartet, dass analog zu 2.) bzw analog zur clear_values() Funktion, dass .flip() self mit ner neuen Instanz "überschreibt" (statt merged).
Leider muss ich verstellen, dies ist nicht der Fall (siehe unten). Ich verstehe nicht, wieso und wüsste nicht, wie das Verhalten zu korriegen ist.

Code: Alles auswählen

>>> m = MyDict({'Germany': 'Berlin', 'Greece': 'Athens', 'France': 'Paris', 'England': 'London'})
>>> m
{'Germany': 'Berlin', 'Greece': 'Athens', 'England': 'London', 'France': 'Paris'}
>>> m.clear_values()
>>> m
{'England': None, 'France': None, 'Germany': None, 'Greece': None}
>>> m = MyDict({'Germany': 'Berlin', 'Greece': 'Athens', 'France': 'Paris', 'England': 'London'})
>>> m.flip()
>>> m
{'England': 'London', 'Paris': 'France', 'Athens': 'Greece', 'France': 'Paris', 'Berlin': 'Germany', 'Germany': 'Berlin', 'Greece': 'Athens', 'London': 'England'}
Kann jemand was dazu sagen? Offensichtlich brauch ich hier ein refresh der Konzepte.

Danke, wie immer im Voraus.
BlackJack

@akis.kapo: Ad 1. Also ich finde `super()` gar nicht super. Das löst ein Problem was ich noch nie hatte auf Kosten von IMHO sehr viel zusätzlicher Komplexität. Die `__init__()` sieht auch nahezu sinnfrei aus.

Ad 2. Hier hätte ich wohl selber etwas geschrieben was über die Schlüssel geht und die Werte setzt statt ein neues Wörterbuch aus den Schlüsseln zu erstellen.

Ad 3. Dann hast Du `clear_values()` falsch verstanden, denn hier wird auch "gemerged"*", da aber alle Schlüssel gleich sind, fällt Dir das nicht auf. Grundsätzlich würde ich mal den Aufruf der `__init__()` in Frage stellen denn nirgends ist garantiert das diese Methode ausser einmal beim Erstellen/Initialisieren eines Objekts sinnvoll aufrufbar ist. Nicht mal das nicht schreckliche Dinge passieren wenn man die mehr als einmal aufruft. Vielleicht sterben deinetwegen gerade irgendwo Katzenbabies. ;-)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

__init__ sollte wirklich selbst aufgerufen werden, außer in einer __init__ das Eltern-__init__. Dein __init__ macht ja auch nichts anderes als das Eltern-__init__ aufzurufen.

Code: Alles auswählen

class MyDict(dict):
    def clear_values(self, value=None):
        for key in self.keys():
            self[key] = value
 
    def flip(self):
        items = list(self.items())
        self.clear()
        for value, key in items:
            self[key] = value
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

@Sirius3: Mit dem `value` Parameter wuerde ich `clear_values` aber eher `reset_values` nennen. Der Parameter ist da recht ueberraschend finde ich.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@cofi: die Erweiterung auf beliebige Werte ist so trivial, dass ich sie gleich eingebaut habe, ist damit ähnlicher zu fromkeys. An sich gibt es in einem Dictionary nur einen Wert, der sagt, dass der Key keinen Wert hat, nämlich, dass der Key nicht existiert. Dafür hat dict auch schon eine Funktion: clear.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Die __init__ Methode musst Du nur dann implementieren, wenn Du irgendetwas im Konstruktor machen möchtest. Deine Klasse macht aber nichts im Konstruktor, es wird nur der Konstruktor der Super-Klasse aufgerufen. Würdest Du Deine __init__ Methode einfach weglassen, dann würde genau das Gleiche passieren.

super brauchst Du immer dann, wenn Du von einer Klasse ableitest und Du das Verhalten von bestehenden Methoden der Super-Klasse erweitern willst. Mit super hast Du dann Zugriff auf die ursprüngliche Methode der Super-Klasse. Aber das machst Du in Deiner Klasse ja auch nicht, also kannst Du anstatt
super(MyDict, self).fromkeys(self.keys())
genauso gut
self.fromkeys(self.keys())
schreiben.

Die Methoden der Super-Klasse kann man übrigens auch ohne super aufrufen. Ich finde die Syntax mit super aber besser, weil ich damit auch gleich dokumentiere, dass ich nicht einfach irgendeine Methode einer anderen Klasse aufrufe, als ob es die eigene wäre, sondern dass ich die Methode der Super-Klasse so aufrufe, als ob es die eigene wäre.

Du hast Dir ein Beispiel gesucht, bei dem Du weder __init__ noch super brauchst. Wenn Du mal sehen willst wozu __init__ und super wirklich gut sind, dann schau Dir mal PyQt Beispiele an.

Deine Klasse ist übrigens auch keine Wrapper-Klasse. Eine Wrapper-Klasse hätte intern ein Dictionary als Membervariable und würde alle Methoden zu dieser Membervariablen durchleiten. Die Klasse wäre damit eine Hülle um das Dictionary Objekt herum, ein Wrapper eben. Du leitest aber vom Dictionary ab, d.h. die Klasse hat kein Dictionary, sondern ist ein Dictionary (und ein bisschen mehr oder anders).
a fool with a tool is still a fool, www.magben.de, YouTube
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

@MagBen
Das war gut erklärt, danke.

Trotzdem der Vollständigkeit halber folgende Frage:

Sirius3 hat in seiner flip() das Problem was ich hatte geschickt umgangen, aber wie schon erwähnt, es geht mir nicht um den Code ansich, sondern darum das OO besser zu verstehen.
Daher nochmals gefragt, angenommen, ich will wirklich aus ((v, k) for k, v in self.items()) ein neues dict Objekt erstellen und self damit überschreiben, wie stelle ich es richtig an?

Nochmal zur Erinnerung, das hier war ja falsch:

Code: Alles auswählen

def flip(self):
    self.__init__(((v, k) for k, v in self.items()))
BlackJack

@akis.kapo: `self` kann man nicht ”überschreiben”. An den Namen ist ein Objekt gebunden und das kann man verändern, sofern es nicht ”immutabe” ist. Innerhalb einer Methode ist der Name `self` ein ganz normaler Name wie jeder andere lokale Name auch und die Semantik unterscheidet sich in keiner Weise von der der anderen Namen die als Argumente übergeben wurden.

Ergänzend zur `super()`-Erklärung von MagBen: Damit bekommt man *nicht* ”die” Superklasse, sondern die nächste Superklasse laut „method resolution order” (MRO). Das kann eine direkte Superklasse, muss es aber nicht.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

MagBen hat geschrieben:Die __init__ Methode musst Du nur dann implementieren, wenn Du irgendetwas im Konstruktor machen möchtest.
Nur der Vollständigkeit halber: __init__() ist streng genommen nicht der Konstruktor, sondern ein "Initialisierer", d.h. eine Methode der bereits erzeugten Instanz der Klasse. Das Python-Pendant eines Konstruktors wäre __new__().
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

kbr hat geschrieben:Nur der Vollständigkeit halber: __init__() ist streng genommen nicht der Konstruktor, sondern ein "Initialisierer", d.h. eine Methode der bereits erzeugten Instanz der Klasse. Das Python-Pendant eines Konstruktors wäre __new__().
Wortklauberei. Es bringt nicht viel, den zu implementierenden Konstruktor in Python Intialisierer zu nennen. Es ist viel einfacher den gleichen Konstrukten in Java, C++ und Python die gleichen Namen zu geben. In C++ ist der Konstruktor auch nicht der Konstruktor, denn wenn ich im Konstruktor drin bin (nach "{"), dann ist das Objekt mit all seinen Variablen und Methoden schon da, d.h. streng genommen ist der Konstruktor-Körper (alles zwischen { und }) auch blos ein Initialisierer.

Wenn ich eine Klasse Artikel mit den drei Attributen (Nr, Name, Preis) habe, dann programmiere ich die Initialisierung dieser drei Attribute in allen drei Sprachen im Konstruktor ziemlich ähnlich.
a fool with a tool is still a fool, www.magben.de, YouTube
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

MagBen hat geschrieben:
kbr hat geschrieben:Nur der Vollständigkeit halber: __init__() ist streng genommen nicht der Konstruktor, sondern ein "Initialisierer", d.h. eine Methode der bereits erzeugten Instanz der Klasse. Das Python-Pendant eines Konstruktors wäre __new__().
Wortklauberei. Es bringt nicht viel, den zu implementierenden Konstruktor in Python Intialisierer zu nennen. Es ist viel einfacher den gleichen Konstrukten in Java, C++ und Python die gleichen Namen zu geben. In C++ ist der Konstruktor auch nicht der Konstruktor, denn wenn ich im Konstruktor drin bin (nach "{"), dann ist das Objekt mit all seinen Variablen und Methoden schon da, d.h. streng genommen ist der Konstruktor-Körper (alles zwischen { und }) auch blos ein Initialisierer.
Nein, das ist so nicht korrekt. Ein Objekt vom Typ X in C++ existiert erst nach dem erfolgreichem Durchlauf des Konstrukturs. Vorher hast du (falls vorhanden) ein Objekt vom Typ der Basisklasse von X. Deshalb kann man im Konstruktor auch keine virtuellen Methoden von X aufrufen.

Bei der __init__-Methode hingegen existiert das Objekt bereits. Mit diesem können nun beliebige Dinge angestellt werden. Daher ist der Initialisierer der korrekte Begriff. __new__ hingegen entspricht in etwa dem Konstruktor in C++, da dort das Objekt erst noch erstellt wird. Erst anschließend wird der Initialisator aufgerufen.
Das Leben ist wie ein Tennisball.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

MagBen hat geschrieben:Die Methoden der Super-Klasse kann man übrigens auch ohne super aufrufen.
Nein, kann man nicht. Könnte man sowas tun wäre super() auch überflüssig und würde wahrscheinlich gar nicht erst existieren.

super() erstellt ein Objekt über dass man die Methoden der nächsten Klasse in der MRO aufrufen kann. Welche Klasse das letztendlich ist oder wird lässt sich je nach Hierarchie gar nicht so einfach ermitteln (von ausprobieren mal abgesehen).
BlackJack

@DasIch: Was ist denn dann Deine Definition von „Superklasse”? Ich würde nämlich auch sagen das man *deren* Methoden ohne `super()` aufrufen kann.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

BlackJack hat geschrieben:@DasIch: Was ist denn dann Deine Definition von „Superklasse”? Ich würde nämlich auch sagen das man *deren* Methoden ohne `super()` aufrufen kann.
Meine Intention war weniger konkret zu sagen dass man die Klasse von der man erbt nicht aufrufen kann, sondern eher dass man nicht einfach die Klasse(n) weiter oben in der Hierarchie aufrufen kann. Prinzipiell finde ich aber den Begriff "Superklasse" ohnehin schlecht, gerade weil sich im Kontext mit super() Zusammenhänge aufdrängen die nicht existieren. "Basisklasse" - um "base class" was auch die Python Dokumentation nutzt zu übersetzen - wäre meiner Meinung nach wesentlich besser.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

DasIch hat geschrieben:sondern eher dass man nicht einfach die Klasse(n) weiter oben in der Hierarchie aufrufen kann
Nicht? Und wenn doch:

Code: Alles auswählen

class A(object):
    def tuWas(self):
        print("A tut was")
        
class B(A):
    def tuWas(self):
        print("B tut was")
        
class C(B):
    def tuWas(self):
        A.tuWas(self)
        B.tuWas(self)
        print("C tut was")

C().tuWas()
(Ich mag keine foo-Methoden)

Output:

Code: Alles auswählen

A tut was
B tut was
C tut was
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@MagBen: Das können war eher als dürfen gemeint. Ja, in *Deinem* Beispiel darf man das, aber eben nicht bei jeder Vererbungshierarchie ohne Gefahr zu laufen dass man Methoden ”weiter oben” mehrfach aufruft. In sofern: Man kann die nicht einfach aufrufen und davon ausgehen dass da in jedem Fall das Richtige passiert. *Das* gilt aber eigentlich auch nur wenn man mit Mehrfachvererbung anfängt und auch dann nur wenn man nicht nur eine ”Haupt”-Basisklasse verwendet und ansonsten nur ”Mixin”-Basisklassen. Und das ganze, abgesehen von `object` ganz oben, ohne diamantförmige Vererbung. Oh, und wenn man `super()` verwendet, dann muss man auch in einer Klasse ganz oben, die nur von `object` erbt auch explizit mit `super()` die Methode auf `object` aufrufen. An der Stelle bin ich mir gerade nicht mehr so ganz sicher das man die `__init__()` aus dem ersten Beispiel tatsächlich weglassen darf!? Dieser ganze `super()`-Kram und die MRO ist superscheisse. Mir jedenfalls zu kompliziert.
Antworten