Einstieg in die Objektorientierung

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
Xfd7887a
User
Beiträge: 135
Registriert: Montag 23. Juni 2014, 17:11

Ich möchte gerne in die Objektorientierung einsteigen. Leider gibt mein Buch "Python 3 - Lernen und professionell anwenden" keine mir verständliche Einführung in das objektorientierte Programmieren.

Deshalb wollte ich fragen, ob ihr kostenlose Ressourcen kennt, mit denen man in dieses Thema Stück für Stück einsteigen kann. Gut wäre es, wenn auch Übungsaufgaben oder Beispielprojekte enthalten wären.
Benutzeravatar
pixewakb
User
Beiträge: 1413
Registriert: Sonntag 24. April 2011, 19:43

Ich kann mich gut daran erinnern, dass das für mich auch ein Problem war. Ich habe es dann seinerzeit durch eine Anleitung für php verstanden, die ich lesen musste. Schau mal, ob du nachfolgendes Beispiel in Python verstehst:

Code: Alles auswählen

class Testobjekt(object):

    name = "Rechteck"

    def __init__(self, breite, hoehe):

        self.breite = breite
        self.hoehe  = hoehe

        if breite == hoehe:
            self.name = "Quadrat"
        else:
            self.name = "Rechteck"

    def set_breite(self, breite):
        self.breite = breite
        if self.breite == self.hoehe:
            self.name = "Quadrat"
        else:
            self.name = "Rechteck"

    def set_hoehe(self, hoehe):
        self.hoehe = hoehe
        if self.breite == self.hoehe:
            self.name = "Quadrat"
        else:
            self.name = "Rechteck"

    def get_breite(self):
        return self.breite

    def get_flaeche(self):
        return self.breite * self.hoehe

    def get_name(self):
        return self.name

if __name__ == "__main__":

    t = Testobjekt(12, 2)

    print("Es handelt sich um ein {}.".format(t.get_name()))

    print("Die Fläche beträgt {} cm².".format(t.get_flaeche()))

    t.set_hoehe(12)

    print("Es handelt sich um ein {}.".format(t.get_name()))

    print("Die Fläche beträgt {} cm².".format(t.get_flaeche()))
Wenn ich gefragt würde: Um Objektorientierung wird zunächst zu viel Wind gemacht. Das Konzept an sich ist sehr einfach und hat enorme Vorteile, wenn man es versteht und verwenden kann. Aber das heißt eben nicht, dass Objektorientierung an sich komplex ist. Meine Erfahrung ist, dass die gängigen Einführungen Objektorientierung an möglichst komplexen Beispielen erklären, vielleicht um die Möglichkeiten des Konzepts aufzuzeigen, was aber Einsteigern nicht hilft.

Versuch mal Beispiele aus deinem Umfeld, womit du dich typischerweise beschäftigst entsprechend umzusetzen. Das ist es dann eben. Im Kern nutze ich init- sowie die set- und get-Methoden und packe dorthin meine Programmlogik. Versuch mal eine Spielerfigur eines Spiels zu implementieren. Die hat dann Lebenspunkte, Hitpunkte und Schildpunkte und kann zuschlagen und einstecken und durch Zaubertränke kann man die Lebenspunkte oder die Hit- oder Schildpunkte erhöhen und dann kann man halt noch fragen, ob sie noch lebt, wie der Name ist und welcher Gilde sie angehört o. ä. So etwas kann man leicht mittels Objektorientierter Programmierung umsetzen.

Nur am Rande: Obiges Beispiel ist wahrscheinlich nicht gut und nicht flüssig, aber bis jemand von den Experten antwortet, ist es vielleicht illustrierend.
BlackJack

@pixewakb: Also für Python ist das Beispiel wirklich nicht gut, denn da würde man keine Getter/Setter schreiben sondern Properties verwenden. Und zwar für `name`, denn die Codewiederholung in beiden Settern ist unschön, das würde man auch mit Gettern und Settern nicht so machen.

Das Klassenattribut `name` wird nicht benutzt und gehört da auch nicht hin.

Edit: Das Beispiel in „pythonisch“:

Code: Alles auswählen

class Form(object):
 
    def __init__(self, breite, hoehe):
        self.breite = breite
        self.hoehe = hoehe
 
    @property
    def name(self):
        return 'Quadrat' if self.breite == self.hoehe else 'Rechteck'
 
    @property
    def flaeche(self):
        return self.breite * self.hoehe


def main():
    form = Form(12, 2)
    print('Es handelt sich um ein {0.name}.'.format(form))
    print('Die Fläche beträgt {0.flaeche} cm².'.format(form))
    form.hoehe = 12
    print('Es handelt sich um ein {0.name}.'.format(form))
    print('Die Fläche beträgt {0.flaeche} cm².'.format(form))


if __name__ == '__main__':
    main()
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Statt `name` möchte man wahrscheinlich auch viel eher eine `ist_quadratisch` property haben die ``self.breite == self.hoehe`` zurückgibt.
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Objektorientierte Programmierung, wie eigentlich irgendeine Programmierung, lernt man am besten, wenn man selbst ein Problem lösen will. Dann stellt man sich erst die Frage, wie kann ich das Problem lösen und als nächstes dann, wie kann ich es besser lösen. In Python ist alles ein Objekt. Leider sind viele Beispiele einfach nur Java-Beispiele nach Python umgeschrieben. Und viele Konzepte und Pattern, die man in Java braucht, sind in Python nur überflüssig. Z.B. set_xxx und get_xxx-Methoden. Falls man das tatsächlich braucht, schreibt man das als Properties

Code: Alles auswählen

class Testobjekt(object):
    def __init__(self, breite, hoehe):
        self._breite = breite
        self._hoehe = hoehe
        self.name = self._determine_name()

    def _determine_name(self): 
        return "Quadrat" if self.breite == self.hoehe else "Rechteck"

    @property
    def breite(self):
        return self._breite
    @breite.setter 
    def breite(self, breite):
        self._breite = breite
        self.name = self._determine_name()

    @property
    def hoehe(self):
        return self._hoehe
    @hoehe.setter 
    def hoehe(self, hoehe):
        self._hoehe = hoehe
        self.name = self._determine_name()

    @property
    def flaeche(self):
        return self.breite * self.hoehe

def main():
    t = Testobjekt(12, 2) 
    print("Es handelt sich um ein {0.name}.".format(t))
    print("Die Fläche beträgt {0.flaeche} cm².".format(t))
    t.hoehe = 12
    print("Es handelt sich um ein {0.name}.".format(t))
    print("Die Fläche beträgt {0.flaeche} cm².".format(t))

if __name__ == "__main__":
    main()
Und dann sieht man, dass eigentlich name dann berechnen kann, wenn man es braucht und vereinfacht das zu:

Code: Alles auswählen

class Testobjekt(object):
    def __init__(self, breite, hoehe):
        self.breite = breite
        self.hoehe = hoehe

    @property
    def name(self): 
        return "Quadrat" if self.breite == self.hoehe else "Rechteck"

    @property
    def flaeche(self):
        return self.breite * self.hoehe
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Ein Quadrat ist idR aus objektorientierter Sicht kein Rechteck, weil sie unzterschiedliche Schnittstellen haben. Wenn ich bei einem Rechteck die Breite ändere, erwarte ich, dass die Höhe unverändert bleibt. Bei deiner Quadrat-Implementierung ist das anders. Das widerspricht dem Liskov-Substitutions-Prinzip.

Man kann sich aber objektorientiert ein Quadrat aus einem Rechteck bauen:

Code: Alles auswählen

class Rectangle:

    def __init__(self, left, top, width, height):
        self.left = left
        self.top = top
        self.width = width
        self.height = height


class Square:

    def __init__(self, left, top, size):
        self._rect = Rectangle(left, top, size, size)

    @property
    def left(self):
        return self._rect.left

    @left.setter
    def left(self, value):
        self._rect.left = value

    @property
    def top(self):
        return self._rect.top

    @top.setter
    def top(self, value):
        self._rect.top = value

    @property
    def size(self):
        return self._rect.width

    @size.setter
    def size(self, value):
        self._rect.width = value
        self._rect.height = value
Zuletzt geändert von pillmuncher am Sonntag 20. September 2015, 17:36, insgesamt 1-mal geändert.
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@pillmuncher: so ganz passt das mit der Komposition aber nicht, ein Quadrat hat kein Rechteck sondern ist ein Rechteck:

Code: Alles auswählen

class Rectangle:
 
    def __init__(self, left, top, width, height):
        self.left = left
        self.top = top
        self.width = width
        self.height = height
 
    @property
    def area(self):
        return self.width * self.height
 
class Square(Rectangle):
    def __init__(self, left, top, size):
        super(Square, self).__init__(left, top, size, size)

    @property
    def height(self):
        return self.width
 
    @height.setter
    def height(self, value):
        self.width = value
Benutzeravatar
pixewakb
User
Beiträge: 1413
Registriert: Sonntag 24. April 2011, 19:43

Was genau kann man jetzt Xfd7887a antworten?

PS Danke fürs Feedback! Wahrscheinlich komme ich auf längere Sicht erst einmal noch nicht um set- und get-Methoden herum. (Weil mein Lehrbuch das auch verwendet hatte und es für mich im Moment noch verständlich ist, was dort passieren soll.) Als "Lernender" darf ich mal anmerken, dass das mit set und get schon anspruchsvoll ist. Die Sache mit properties dürfte für Xfd7887a und war für mich anfangs deutlich zu hoch. Ich nutze übrigens properties jetzt so langsam im Zuge des Code Refactorings.
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Sirius3 hat geschrieben:@pillmuncher: so ganz passt das mit der Komposition aber nicht, ein Quadrat hat kein Rechteck sondern ist ein Rechteck
Dann müsste das ja funktionieren:

Code: Alles auswählen

def increase_width_but_not_height_of_rectangle(rect):
    old_width = rect.width
    old_height = rect.height
    rect.width *= 2
    assert rect.width == 2 * old_width
    assert rect.height == old_height
Ergebnis:

Code: Alles auswählen

>>> increase_width_but_not_height_of_rectangle(s)                               
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in increase_width_but_not_height_of_rectangle
AssertionError
Wie schon gesagt, das Liskov-Substitutions-Prinzip wird verletzt. Jedenfalls dann, wenn man annimmt, dass die Invariante bzgl. der Änderungs-Unabhängigkeit von Höhe und Breite gilt.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Sirius3: Wenn die Rechtwinkeligkeit der Ecken das Kriterium der Rechteckigkeit wäre, dann wäre auch das hier ein Rechteck:Bild
Oder anders gesagt: man muss vorher definieren, was als Rechteck und Quadrat gelten soll, bevor man festlegt, was Basisklasse und was abgeleitete KLasse sein soll, oder ob so eine Beziehung überhaupt besteht. Und diese Entscheidung muss man auf Basis davon treffen, was man mit den Objekten später anfangen will, nicht davon, ob es in Platons Ideenhimmel in metaphysisch absolutem Sinne wahr ist.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pixewakb
User
Beiträge: 1413
Registriert: Sonntag 24. April 2011, 19:43

@Xfd7887a

Ich kenne leider keine verständliche Einführung in die objektorientierte Programmierung mit Python, aber seinerzeit hatte mir die Einleitung von Peter Kropff - allerdings für php - sehr geholfen.

OOP PHP5

Versuch nicht zwingend den php-Quellcode zu verstehen, aber lies die erläuternden Texte und versuch mal zu verstehen, was dort gemacht wird. Er schreibt *flott* und erhellend. Wenn Du das grob verstehst, dann kannst du möglicherweise mal eigene Gehversuche in Python unternehmen. Wenn das im Kern läuft, also etwa Objekt Hund, Methode trinken, Attribut durstig = True oder False usw., dann kannst du darauf aufbauend komplexere Sachen angehen.

Der Vollständigkeit halber Python Doc auf Deutsch (3.3), allerdings nicht unbedingt das, was ich als Anfänger suchen würde.
BlackJack

@pixewakb: Den Text finde ich nicht wirklich gut. Der ist für PHP, vieles davon ist in Python nicht machbar/abbildbar und das was geht ist nicht wirklich „pythonisch“ *und* die OOP-Beispiele sind teilweise grottenschlecht — und zwar unabhängig von der Programmierpsrache. Was man dort lernt ist die PHP-Syntax und -Besonderheiten für Klassen aber gar nicht wie man OOP nun eigentlich einsetzt beziehungsweise werden dort Sachen gezeigt die schlechtes OOP sind.

Man kommt mit Funktionen in Python weiter als in PHP, darum kann ich das was er über strukturierte Programmierung mit Funktionen sagt nicht 100%ig unterschreiben. Andererseits treffen fast alle Punkte die er über Klassen sagt auch auf Funktionen zu:

* Sie sind so konzipiert das man die innere Logik nicht kennen muss.

* Modifikationen innerhalb der Funktionen betreffen nicht den übrigen Code.

* Funktionen werden so entwickelt das sie jederzeit wiederverwendet werden können. Also so weit das möglich oder sinnvoll ist, das gilt aber auch für Klassen. Dieses pauschale Heilsversprechen von OOP das man dann alles irgendwie wiedervernden kann stimmt letztlich ja auch nicht.

Bei den letzten beiden Punkten stimmt der erste dann halt nicht mehr für Funktionen: Man kann sie nicht durch Vererbung erweitern.

Den letzten Punkt verstehen ich nicht: Klassen enthalten bereits eine Fehlerroutine? Häh?

Für Python-Programmierer ist es etwas zu PHP-syntaxlastig (man benutzt ``->`` aber braucht kein ``$``, der Gültigkeitsoperator ``::``, …).

Das Briefbeispiel ist zum schreiend wegrennen. Eine Klasse `BriefZustellen` mit den Attributen `adresse`, `inhalt`, und `versendet` und den Methoden `werfBriefEin()`, `sortierBriefEin()`, und `stellBriefZu()`? Argh.

Insgesamt gibt's da auch viel über ``private``, ``protected``, ``public`` was so nicht auf Python übertragbar ist. Inklusive der Getter-/Setter-Methoden die man in Python nicht schreiben würde.

Und es gibt die Klasse `Mensch` mit den Methoden `getName()` und `setName()` und davon abgeleitet `Frau` mit den Methdoden `setzNamen()` und `holeNamen()`, also *andere* Namen! WTF!?

Die Klasse `Sex` ist aus objektorientierter Sicht vollkommen sinnlos. Und wenn man Typprüfungen vornimmt ist das auch fast immer ein Zeichen das man OOP nicht so anwendet wie es vorgesehen ist. Die Frage ist nämlich an der Stelle warum es Klassen für `Mann` und `Frau` gibt und warum das Geschlecht nicht einfach eine Eigenschaft ist.

Bei den magischen Methoden ist fast alles nicht auf Python anwendbar.

Eine Klasse = eine Datei sollte man sich in Python dann ganz schnell wieder abgewöhnen.

Der Hinweis bei dem `Getraenk`-Beispiel: „Im Sinne der objektorientierten Programmierung ist das Beispiel ziemlich unglücklich“ aber „Es geht nur um das Verständnis.“ ist ja echt drollig. Was soll man denn da verstehen? Wie man es *nicht* macht? Dazu müsste dann doch aber dabei stehen warum man das nicht macht und wie man es machen würde, und dann bräuchte man das furchtbar schlechte Beispiel nicht. Keine Ahnung wofür man in PHP statische Methoden verwendet, aber in anderen Sprachen üblicherweise als alternative Konstruktoren. Insbesondere bei Sprachen bei denen es keine Überladung von Methoden gibt die anhand von Typsignaturen unterschieden werden können.

Schnittstellen lässt sich wieder nicht auf Python übertragen und abstrakte Klassen auch nicht so wirklich.

Iteratoren sind interessant, aber die Schnittstelle ist in PHP anders und deutlich umständlicher als in Python. Ausserdem ist das Beispiel schlecht gewählt. Da sollte man eher einen typischen Containertyp zeigen und keine Frau über deren Handtascheninhalt iteriert wird.

Ach so: Insgesamt ist der Text nichts für Frauen/Mädchen, es sei denn sie möchten gerne als Objekte betrachtet werden. Klasse hat der Text dort nicht wirklich…
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Zwei kleine Einführungen zu OO mit Python. Beide fangen nicht mit den Schlüsselwörtern an, sondern beginnen mit dem Wozu.
http://inventwithpython.com/blog/2014/1 ... e-example/
http://reeborg.ca/docs/oop_py_en/index.html
a fool with a tool is still a fool, www.magben.de, YouTube
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Richtige Frauen/Mädchen/Männer/Jungs machen das so:

Code: Alles auswählen

# constructor:
def address(first_name, last_name, street, zipcode, city):
    def address_data(index):
        if index == 0:
            return first_name
        elif index == 1:
            return last_name
        elif index == 2:
            return street
        elif index == 3:
            return zipcode
        elif index == 4:
            return city
        else:
            raise AttributeError
    return address_data

# selectors:
def first_name(address_data):
    return address_data(0)

def last_name(address_data):
    return address_data(1)

def street(address_data):
    return address_data(2)

def zipcode(address_data):
    return address_data(3)

def city(address_data):
    return address_data(4)

def main():
    addr = address('Donald', 'Duck', 'Erpelweg 13', '12345', 'Entenhausen')
    assert 'Donald' == first_name(addr)
    assert 'Duck' == last_name(addr)
    assert 'Erpelweg 13' == street(addr)
    assert '12345' == zipcode(addr)
    assert 'Entenhausen' == city(addr)

if __name__ == '__main__':
    main()
Pfff! Objektorientierung mit Objekten.

Dank an Hal Abelson, von dem ich das gelernt habe.
In specifications, Murphy's Law supersedes Ohm's.
nezzcarth
User
Beiträge: 1734
Registriert: Samstag 16. April 2011, 12:47

pixewakb hat geschrieben: Ich kenne leider keine verständliche Einführung in die objektorientierte Programmierung mit Python, aber seinerzeit hatte mir die Einleitung von Peter Kropff - allerdings für php - sehr geholfen.
MagBen hat geschrieben:Zwei kleine Einführungen zu OO mit Python. Beide fangen nicht mit den Schlüsselwörtern an, sondern beginnen mit dem Wozu.
http://inventwithpython.com/blog/2014/1 ... e-example/
http://reeborg.ca/docs/oop_py_en/index.html
Ich find' "Python 3 Object Oriented Programming" macht keinen so schlechten Eindruck. Ob das Buch im Forum schon mal rezipiert wurde, weiß ich allerdings nicht.
Xfd7887a
User
Beiträge: 135
Registriert: Montag 23. Juni 2014, 17:11

Danke für die vielen Antworten. Ich werde sie die nächsten Tage durcharbeiten.
Antworten