@pot: Kapitel 12.1.2: `__del__()` sollte man entweder gar nicht erwähnen, oder in einer Warnung, dass man es nicht verwenden soll. Der Text behandelt es aber wie einen Destruktor in C++, ohne jegliche Warnung, dass die ganzen Probleme, die man sich damit einhandeln kann, und die Garantien, die die Sprache (nicht) macht, diese Methode letztendlich nutzlos bis gefährlich machen.
Die Erklärung `Destruktoren werden aber häufig benötigt, um beispielsweise bestehende Netzwerkverbindungen sauber zu trennen, den Programmablauf zu dokumentieren oder Fehler zu finden.` ist jedenfalls falsch. Wenn man eine Ressource sauber abräumen will, kann man sich nicht aus `__del__()` verlassen, da weder garantiert wird, wann die Methode aufgerufen wird, noch ob sie *überhaupt* jemals aufgerufen wird. Den Programmablauf dokumentieren, ich nehme mal an hier ist protokollieren der Art "Objekt xy wurde gerade zerstört" gemeint, geht mit `__del__()` folglich auch nicht. Und eine Methode zur Fehlersuche zu verwenden, die durch ihre blosse Existenz schon drastischen Einfluss auf das Verhalten des Programms bezüglich der Speicherfreigabe haben kann, halte ich auch für keine gute Idee.
Die Erklärung von `__init__()` als Konstruktor ist technisch gesehen falsch. Der echte Konstruktor heisst in Python `__new__()`.
Das Ende des Kapitels lässt den Horror ahnen, der im Nächten folgt: `Allerdings ist es immer noch möglich, außerhalb der Klasse auf die Attribute direkt zuzugreifen und diese zu verändern, […]` und `Auch die Zuweisung von Werten ungültiger Datentypen wird noch nicht verhindert.`
Damit verlassen die Autoren die Welt der Python-Programmierung und betreten die Abgründe der "discipline-and-bondage"-Sprachen. Wer statische Typisierung und Zugriffsschutz mag, soll doch bitte nicht in Python programmieren und erst recht keine Bücher darüber schreiben.
In Kapitel 12.1.3 werden dann auch erst einmal alle Attribute mittels doppelter führender Unterstriche "private" gemacht, um gleich darauf einen trivialen Getter ein zu führen. Bezeichnung dafür ist `private members`. Irgendwie nicht die übliche Python-Nomenklatur ─ warum können die nicht bei "Attribut" oder "attribute" bleiben? Das die beiden Unterstriche dazu gedacht sind, Namenskollisionen bei Mehrfachvererbung zu vermeiden, wird weder hier, noch später im Abschnitt über Vererbung erwähnt.
Danach folgt ein Setter, der das Argument mit `type()` auf `float` oder `int` prüft. Was a) dem "duck typing" zuwieder läuft und b) selbst für eine explizite Prüfung schlechter Stil ist, weil damit völlig unnötigerweise auch alle Objekte ausgeschlossen werden, deren Typ von `int` oder `float` abgeleitet wurde.
Getter und Setter werden in 12.1.4 durch Properties ersetzt, wobei diese im Text als `Managed Attributes` bezeichnet werden.
Das Beispiel für `Statische Member` ist wieder komplett an der Realität vorbei, weil man `__del__()` so eben nicht zuverlässig verwenden kann. Man kann damit sehen wieviele Exemplare von `Konto` gerade existieren, sieht aber nicht, welche davon wirklich von der Bank verwendet werden oder welche aus verschiedenen anderen Gründen noch im Speicher herumhängen.
Statische Methoden werden als relativ Nutzlos hingestellt, dabei werden sie ─ und Klassenmethoden, die gar nicht erwähnt werden ─ häufig für alternative Konstruktoren verwenden.
Wenn wir schon "private" Attribute haben und auf Typen prüfen, darf in 12.3.1 `__slots__` als Mittel zum verhindern vom dynamischen hinzufügen von Attributen nicht fehlen. Der eigentliche Zweck, dass Exemplare mit "slots" weniger Speicherplatz benötigen, wird erst an zweiter Stelle erwähnt.
Und gerade wo man denkt es kann nicht schlimmer werden, kommt am Ende, unter der Überschrift `Objektphilosophie`, noch ein Beispiel wie konsequente Objektorientierung es erleichtert wiederverwendbaren Code zu schreiben, das so schlecht ist, dass es einem die Sprache verschlägt:
Code: Alles auswählen
class ListeMitDurchschnitt(list):
def durchschnitt(self):
summe, i = 0.0, 0
for e in self:
if type(e) in (int, float):
summe += e
i += 1
return summe / i
IMHO ein klarer Fall wo eine generische Funktion viel flexibler ist, als eine Liste um diese Methode erweitert. Folgende Funktion funktioniert mit jedem "iterable" und allem was sich addieren und durch eine ganze Zahl teilen lässt. Zum Beispiel auch mit `decimal.Decimal`- oder `gmpy.mpq`-Objekten.
Code: Alles auswählen
def average(iterable, start_value=0):
total = start_value
i = None
for i, value in enumerate(iterable):
total += value
if i is None:
raise ValueError('iterable must contain at least one argument.')
return total / (i + 1)