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.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Dienstag 9. September 2014, 16:08

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

Dienstag 9. September 2014, 16:24

@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: 8625
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 9. September 2014, 19:15

__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
Moderator
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Dienstag 9. September 2014, 22:08

@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: 8625
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 10. September 2014, 07:52

@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: 786
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Mittwoch 10. September 2014, 09:50

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

Mittwoch 10. September 2014, 09: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

Mittwoch 10. September 2014, 11:09

@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: 949
Registriert: Mittwoch 15. Oktober 2008, 09:27

Mittwoch 10. September 2014, 12:43

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: 786
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Mittwoch 10. September 2014, 13:50

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: 4871
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Mittwoch 10. September 2014, 14:18

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: 2473
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Mittwoch 10. September 2014, 14:43

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

Mittwoch 10. September 2014, 14:46

@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: 2473
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Mittwoch 10. September 2014, 15:06

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: 786
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Mittwoch 10. September 2014, 15:20

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
Antworten