Seite 2 von 3

Verfasst: Freitag 7. März 2008, 17:58
von pot
Leonidas hat geschrieben:Ja, das Kapitel gibt einen guten Überblick darüber, wie man es falsch macht. Schade dass die dahinter nicht geschrieben haben "Haha, war alles nur Spaß, hier wie man das in Python machen würde: ...".
Das steht dann auch im nächsten Kapitel:

http://www.galileocomputing.de/openbook ... .htm#t2t32

Grüsse
Pot

Verfasst: Freitag 7. März 2008, 18:09
von BlackJack
@pot: Ich glaube Du hast es nicht ganz verstanden: Den Link, den Du da angibst, da fängt es ja gerade an schrecklich zu werden! Ab da wird gezeigt wie man OOP in Python *nicht* machen sollte.

Verfasst: Freitag 7. März 2008, 18:11
von pot
Ich hab' es mir zwar nicht wirklich durchgelesen, bin jedoch nicht so erfahren das zu bestätigen. Was ist denn daran so falsch?

Grüsse
Pot

Verfasst: Freitag 7. März 2008, 18:25
von Leonidas
pot hat geschrieben:Ich hab' es mir zwar nicht wirklich durchgelesen, bin jedoch nicht so erfahren das zu bestätigen. Was ist denn daran so falsch?
Müssen wir diese Diskussion in jedem Thread wiederholen? Schau doch einfach nach was bereits gesagt wurde.

Verfasst: Freitag 7. März 2008, 20:17
von lunar
gerold hat geschrieben:
lunar hat geschrieben:
Denn öffnen kann ich viel! Aber mit ``list`` bekomme ich eine Liste zurück. Mit ``dict`` bekomme ich ein Dictionary. Und mit ``file`` bekomme ich ein File.
... das, was ``file`` zurückgibt, ist in meinen Augen kein Dateiobjekt, da es lediglich den Inhalt wiederspiegelt. Ich erhalte über ``file``keinerlei Zugriff auf die Metainformationen einer Datei (Größe, Rechte, Eigentümer, etc.)
Hallo lunar!

Für die Metainformationen einer Datei wäre dann ``fileinfo`` zuständig. ;-)
Dann aber zwecks Konsistenz bitte auch ``fileopen`` oder ``FileStream`` anstatt ``file``... und dann sind wir vollends bei .NET angekommen ;)

Verfasst: Freitag 7. März 2008, 20:30
von sea-live
MECKER


ICH weis Ja nicht wie ihr das Hier Handhabt
Ich hatte einen THread mit OOP eröffnet und nun wird über SCHLANGE 3.0 Diskutiert
ist das hier so Sitte völlig abseits vom Thread_THEMA
EIN NEULING

Verfasst: Freitag 7. März 2008, 20:44
von audax
Geh. Weg.

Verfasst: Freitag 7. März 2008, 20:46
von BlackJack
@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)

Verfasst: Freitag 7. März 2008, 20:57
von sea-live
Ein Buch(Programm) ist nur so gut wie der Autor(Programmierer)

und selbst BlackJack lernt immer noch was dazu (denk ich jedenfals)

es gibt immer einer der es besser weis
und hinterher ist man sowieso schlauer

ALBERT EINSTEIN
Holzhauen ist so beliebt weil man das ergebniss gleich sieht!

Verfasst: Freitag 7. März 2008, 21:00
von Granino
Die Hoffnung stirbt zuletzt sagte mein OOP Prof man kann auch noch mit 50
Und was sagt Dein Prof zur Obergrenze?
Vielleicht verstehe ich dann, woher meine Problleme stammen.
Gruß Granino

Verfasst: Freitag 7. März 2008, 21:02
von audax
sea-live hat geschrieben:Ein Buch(Programm) ist nur so gut wie der Autor(Programmierer)

und selbst BlackJack lernt immer noch was dazu (denk ich jedenfals)

es gibt immer einer der es besser weis
und hinterher ist man sowieso schlauer

ALBERT EINSTEIN
Holzhauen ist so beliebt weil man das ergebniss gleich sieht!
Wenn man die Wahl zwischen Scheiße und Gold hat...

Man muss nicht unbedingt ein Buch durcharbeiten, nur um zu wissen, wie man es nicht macht.

Verfasst: Freitag 7. März 2008, 21:08
von numerix
Danke BlackJack!
Für diese ausführliche und konkrete Bewertung, die für mich einige Denkanstöße, neue Erkenntnisse und auch Fragen mit sich bringt.

Ich fang mal an damit:

Die Bezeichnung von __init__() als Konstruktor oder auch Konstrukturmethode (was ich irgendwo anders als Bezeichnung gelesen habe) finde ich - auch wenn sie nach deiner Aussage "technisch falsch" ist - nicht schlecht, weil es von der Sache her doch den Punkt trifft und beim Verständnis für jemanden, der von einer anderen OOP Sprache kommt, hilfreich ist. Eine Methode namens __new__() habe ich bisher noch nirgends zu Gesicht bekommen.

Warum soll, "wer Zugriffsschutz mag nicht in Python programmieren"???
Es ist doch grundsätzlich sehr vernünftig, wenn man auf (Daten-)Attribute nicht direkt, sondern nur über Methoden (getter/setter) zugreift - zumindest für den schreibenden Zugriff.
Bis eben war ich davon ausgegangen, dass die Einführung der property() Funktion genau diesem Zweck (= Zugriffsschutz) dient, allerdings mit dem Vorteil für den Nutzer der Klasse, dass er für den Zugriff auf die (Daten-)Attribute die Getter-/Setter nicht selbst explizit aufzurufen braucht, sondern (scheinbar) direkt auf die Attribute zugreifen kann.

In einem Satz unterscheidest du statische Methoden und Klassenmethoden. Für mich war das immer ein und dasselbe. Erklärung wäre nett.

Verfasst: Freitag 7. März 2008, 21:12
von sea-live
Hyperlinks zu besseren Docus
sind Hier erwünscht und das Ziel dieses Threades

Mein Gedrucktes stammt aus 1994 Kompendium C 2500Seiten

ich wolte ja in C dann hat jemand gemailt mach SCHLANGE statt 3seiten C 3Zeilen

Code: Alles auswählen

import urllib
url=("http://ichart.yahoo.com/table.csv?s=%s&a=%s&b=%s&c=%s&d=%s&e=%s&f=%s&g=d&ignore=.csv" %(symbol_liste[x],Monatvon,Tagvon,Jahrvon,Monatbis,Tagbis,Jahrbis))
           
            req = urllib.urlopen(url)

           
deswegen bin ich hier gelandet

dann sagte jemand nimm Tkinter tolle oberfläche GUI
dann mit Chart_zeichner nimm wxPython

usw
ergebniss
Bild

Verfasst: Freitag 7. März 2008, 21:35
von audax
Ich will dir jetzt wirklich nicht zu nahe treten: Ist Deutsch deine Muttersprache oder bist du noch am Lernen?
Mir fällt es ernsthaft schwer, den Sinn deiner Sättze zu erraten.

Verfasst: Freitag 7. März 2008, 22:03
von BlackJack
@pütone: `__new__` braucht man, wenn man den "echten" Konstruktor von "immutable"s überschreiben möchte. Völlig sinnloses Beispiel:

Code: Alles auswählen

def MyInt(int):
    def __init__(self, value):
        int.__init__(self, 2 * value) # Geht nicht!
Zu dem Zeitpunkt wo die `__init__()` aufgerufen wird, existiert das Objekt schon und man kann den Wert nicht mehr ändern. Dafür muss man `__new__()` überschreiben. Und das ist ein echter Konstruktor in dem Sinne, das man dort wirklich ein Objekt erzeugen kann und zurückgeben muss.

Andere Einsatzzwecke sind "Singletons", oder das "Flyweight"-Entwurfsmuster.

Properties sind dazu da um aus einfachen Attributen noch nachträglich "berechnete Attribute" machen zu können, ohne das sich die API ändert. Dieses Bankkonto-Beispiel ist IMHO irreführend, weil damit suggeriert wird, dass man natürlich nicht möchte, dass jemand von aussen den Kontostand ändern kann. Nur das man in einem Programm, das diese Objekte verwendet, ja gar nicht von aussen kommt! Die Kapselung ist nicht zum Schutz der Daten da, sondern um Flexibilität bei der Implementierung der Interna zu waren. Wenn der Räuber Hotzenplotz aus dem Buch in der Lage ist beliebige Konto-Objekte zu erstellen, dann ist aus Sicherheitsaspekten eh alles zu spät. Dann kann er sich auch ein neues Konto erzeugen und alles von den anderen dorthin überweisen, auch wenn er keinen direkten Zugriff auf das Attribut `__Kontostand` hat.

Ein Unterstrich in `_kontostand` sollte reichen, damit keiner auf die Idee kommt von aussen direkt darauf zuzugreifen, ohne zu wissen was er da tut.

Statische Methoden sind vom Verhalten her einfach nur Funktionen, die im Namensraum einer Klasse stecken. Klassenmethoden bekommen als erstes Argument automatisch die Klasse als Argument auf der sie aufgerufen werden. Also ein ähnlich "magisches" Argument wie `self` bei normalen Methoden. Beispiel:

Code: Alles auswählen

class A(object):
    def __init__(self, arg):
        print 'init A', arg
    
    @classmethod
    def blub(cls, arg):
        return cls(arg)


class B(A):
    def __init__(self, arg):
        print 'init B', arg
        A.__init__(self, arg)


def main():
    x = B.blub(42)
    print type(x)

Verfasst: Sonntag 9. März 2008, 21:11
von pot
Danke BlackJack für die ausführliche Erklärung. Jetzt weiss ich, wo ich mein Wissen sicherlich nicht erweitern werde: bei diesem Buch :lol:

Eigentlich wollte ich nicht so drastisch vom Thema abkommen, Entschuldigung dafür.

Grüsse
Pot