Frage zu OO Vererbung bei Methoden

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
Swifty91
User
Beiträge: 2
Registriert: Mittwoch 5. September 2018, 10:01

Hallo zusammen

Ich spiele gerade einwenig mit OO herum und habe kurz das Beispiel von Wikipedia nachgebastelt.
Nun möchte ich bei meiner Klasse Amphibienfahrzeug eine Methode ergänzen welche ursprünglich zur Klasse Landfahrzeug gehört.
Wenn ich nun die Super funktion direkt nach dem def benutze:

Code: Alles auswählen

def fahren(self, geschwindigkeit):
        super().fahren(geschwindigkeit)
        if self.meter > 0:
            print("Du kannst nicht fahren wenn du am tauchen bist")
wird zuerst der ursprüngliche Teil der Funktion ausgeführt, das Fahrzeug hat die Geschwindigkeit 50 und erst danach wird überprüft ob das Fahrzeug nicht schon abgetaucht ist.

Gelöst habe ich es mit der else Ergänzung. Ich zweifle aber ob dass eine schöne Lösung ist. Was meint ihr?

Code: Alles auswählen

    def fahren(self, geschwindigkeit):
        if self.meter > 0:
            print("Du kannst nicht fahren wenn du am tauchen bist")
        else:
            super().fahren(geschwindigkeit)

Hier noch der komplette Code:

Code: Alles auswählen

# -*- coding: UTF-8 -*

class Landfahrzeug():
    def __init__(self):
        self.__raeder = 4
        self.__maxgeschwindigkeit = 120
        
    def fahren(self, geschwindigkeit):
        self.geschwindigkeit = geschwindigkeit
        if self.geschwindigkeit > 0 and self.geschwindigkeit < self.__maxgeschwindigkeit:
            print("Das Fahrzeug fährt " + str(self.geschwindigkeit) + " km/h")
        elif self.geschwindigkeit > self.__maxgeschwindigkeit:
            print("Das Fahrzeug kann nicht schneller als 120 km/h fahren")
        else:
            print("Das Fahrzeug kann nicht langsamer als 0 km/h fahren ")

class Wasserfahrzeug():
        def __init__(self):
            self.__propeller = 2
            self.__maxtauchgang = 20
            self.meter = 0

        def  tauchen(self, meter):
            self.meter = meter
            if self.meter < self.__maxtauchgang:
                print("Das Fahrzeug taucht ab auf " + str(self.meter) + " meter")
            else:
                print("So tief kann das Fahrzeug nicht tauchen")

class Amphibienfahrzeug(Landfahrzeug, Wasserfahrzeug):
    def __init__(self):
        Landfahrzeug.__init__(self)
        Wasserfahrzeug.__init__(self)
    
    def fahren(self, geschwindigkeit):
        if self.meter > 0:
            print("Du kannst nicht fahren wenn du am tauchen bist")
        else:
            super().fahren(geschwindigkeit)

Instanz = Amphibienfahrzeug()
Instanz.tauchen(5)
Instanz.fahren(50)
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Lass das else einfach weg und rücke den Teil mit super() entsprechend um eine Ebene nach links ein. Wenn du willst, dass die Funktion bei fehlgeschlagener Prüfung direkt aussteigt, dann befasse dich mal mit dem Thema Exceptions. Sofern dir das noch zu kompliziert ist, kannst du auch erstmal ein blankes return für den Ausstieg benutzen.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Swifty91: Du nutzt bereits super() und solltest das auch bei den __init__ Methoden tun, anstatt diese bei Amphibienfahrzeug explizit aufzurufen. Zudem nutzt Du Mehrfachvererbung, was bei komplexeren Konstrukten fehlerträchtig werden kann. Wenn man wirklich Mehrfachvererbung nutzen möchte, ist es sinnvoll sich mit der MRO und dem zugrunde liegenden C3 Algorithmus vertraut zu machen. Oder entscheiden, Mehrfachvererbung anfänglich erst einmal zu vermeiden.

Eine sinnvolle und leichtere Übung wäre, erst einmal ein abstraktes Fahrzeug zu konstruieren und darauf dann ein Land-, oder Wasserfahrzeug aufzubauen. Und den Unterschied zwischen vererben und wrappen zu lernen.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Swifti91: Dein Beispiel illustriert deutlich eine der Fallen, in die man bei der OO-Programmierung rein laufen kann. Klassen sollten offen fuer Erweiterung, aber geschlossen fuer Veraenderung sein. https://en.wikipedia.org/wiki/Open%E2%8 ... _principle

Was heisst das in deinem Fall? Die Entscheidung in einer abgeleiteten Klasse, einfach eine Methode komplett nicht mehr aufzurufen, weil die abgeleitete Klasse das so entschieden hat, ist sehr drastisch und kann zu subtilen Fehlern fuehren. Denn es gibt keinen Weg zu garantieren, dass in Landfahrzeug .fahren nicht etwas passiert, dass nicht einfach unterlassen werden kann.

Wenn man also eine solche Funktionalitaet entwirft wie du es tust, dann wuerde man stattdessen einen Entwurf waehlen, bei dem man eine extra Methode "kann_fahren" einfuehrt. Und die ist dann explizit dazu gedacht ueberladen zu werden, und das dann auch ohne wiederum super aufzurufen. W

Code: Alles auswählen

class Landfahrzeug:
     ...
     def kann_fahren(self):
             return True # immer wahr


     def fahren(self):
            .... # tu was, was imer getan wird
            if self.kann_fahren():
                    ....
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Swifty91: Ergänzend zu kbr: Ich würde von Mehrfachverbung komplett die Finger lassen, ausser für Mixin-Klassen. Alles andere wird schnell recht kompliziert, ohne das sich das rechtfertigen lässt. Komposition statt Vererbung ist da in der Regel flexibler.

Die doppelten führenden Unterstriche sind *nicht* Python's Antwort auf ``private`` in diversen statisch typisierten Programmiersprachen! In Python kennzeichnet man Attribute die nicht teil der öffentlichen API sind mit *einem* führenden Unterstrich. Wobei ich hier kein Attribut sehe was man nicht auch öffentlich machen könnte. Python-Programmierer sind da nicht so verschlossen und vertrauen ihren Mitmenschen. Oder zumindest anderen Programmierern. :-)

Attribute sollten alle in der `__init__()` eingeführt werden. Sonst wird es unübersichtlich und fehleranfällig. Das Fahrzeug hat beispielsweise im Grundzustand auch eine `geschwindigkeit`.

In `fahren()` setzt Du die `geschwindigkeit`, egal was die ``if``-Abfragen ergeben, das heisst es ist Möglich den Wert auf einen ungültigen Wert zu setzen. Das gleiche bei `tauchen()`. (Dort testest Du übrigens nicht auf negative Werte – das Fahrzeug kann dann also auch fliegen? :-))

Die Bedingungen sind so formuliert das mehr Code als nötig erforderlich ist. Wenn man erst die beiden ungültigen Wertebereiche prüft, braucht man einen Vergleich weniger.

Die 120 in der `print()`-Ausgabe sollte durch die tatsächliche Maximalgeschwindigkeit ersetzt werden. Das ist ja ein Attribut, und wenn man dort den Wert ändert, passt der Ausgabetext sonst nicht mehr zur Realität.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Swifty91
User
Beiträge: 2
Registriert: Mittwoch 5. September 2018, 10:01

Zuerst mal danke für eure Inputs.



Folgendes:
kbr hat geschrieben: Mittwoch 5. September 2018, 10:47 @Swifty91: Du nutzt bereits super() und solltest das auch bei den __init__ Methoden tun, anstatt diese bei Amphibienfahrzeug explizit aufzurufen.
Wenn ich aber die Klassen nicht explizit aufrufe sondern ersetze durch super().__init__() so werden anschliessend nur die Attribute von Landfahrzeuge übergeben und ich bekomme dann die Fehlermeldung "AttributeError: 'Amphibienfahrzeug' object has no attribute '_maxtauchgang'"

Muss ich dann bei Super doch noch angeben das er die Attribute von beiden Klassen übernehmen soll?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Du mußt schon in jedem __init__ die super()-Methode aufrufen.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

@Swifty91: Wenn man `super` benutzt, muss man immer davon ausgehen, dass die über `super`aufgerufene Methode nicht die ist, die man eigentlich aus der Liste der Eltern in der Klassendeklaration ablesen würde. Wenn man `super` verwendet, steuern die eigenen Kinder, wessen Methoden man aufruft. Man muss also (bei Mehrfachvererbung) immer davon ausgehen, dass man die `__init__`-Methode der Elternklasse aufrufen muss, auch wenn man selbst gar keine Elternklasse hat. Aber eine Kindklasse könnte eine andere Klasse an eine spätere Stelle der MRO (method resolution order) stellen.

Passend zu dem Thema ein sehenswerter Vortrag von Raymond Hettinger: Super considered super. Und dann gibt’s auch noch die Gegenposition: Python’s Super Considered Harmful.

Ich würde mich aber __blackjack__ anschließen: (Mehrfach-)Vererbung erhöht die Komplexität. Insbesondere als Anfänger, wenn man gerade OOP lernt, hat man schon genug zu lernen. Mehrfachvererbung kann erstmal warten.
Antworten