Objektorientierung und Vererbung mit mehreren Skripten

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.
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

Hallo Community,

ich habe ein Problem mit der Objektorientierung. Ich habe vier Klassen in vier Skripten:

Skript1: Klasse A
Skript2: Klasse B
Skript3: Klasse C
Skript4: Klasse D

Die Klassen A bis C sind Unterklassen der Klasse D (Variablen und Funktionen von D wurden vererbt). Nun möchte ich in meinem Main-Skript ein Objekt der Klasse D erstellen und somit die Funktionen der anderen drei Klassen abarbeiten lassen (dies ist programmtechnisch auch schon realisiert). Wenn ich nun das Main-Skript ausführe, dann erhalte ich folgenden Fehler:

ImportError: cannot import name KlasseD

Ich weiß jetzt bereits, dass dies an den Imports liegt, die wie folgt sind:

Skript1: from Skript4 import KlasseD
Skript2: from Skript4 import KlasseD
Skript3: from Skript4 import KlasseD
Skript4: from Skript1 import KlasseA
from Skript2 import KlasseB
from Skript3 import KlasseC
Main-Skript: from Skript4 import KlasseD

Da beißt sich natürlich die Katze in den Schwanz, allerdings meckert der Interpreter sofort, wenn ich die Imports so nicht mache. Ich möchte im Prinzip der Übersichtlichkeit wegen für jede Klasse ein eigenes Skript haben (so habe ich es auch mal mit den Header- und Source-Files in C und C++ gelernt).

Wie kann ich sowas in Python anwenden, oder macht man das hier nicht in der Art?
BlackJack

@AnyOne: Wenn `A`, `B`, und `C` Unterklassen von `D` sind, warum muss `Skript4` dann die Unterklassen importieren‽ Zirkuläre importe deuten in aller Regel auf einen ungünstigen Entwurf hin. Wenn sich zwei Module zwingend gegenseitig importieren müssen, ist der Inhalt so eng miteinenander verbunden, dass man die Module gar nicht einzeln verwenden kann, sich also auch die Frage stellt warum es dann auf zwei Module aufgeteilt wurde.

Eine Klasse pro Modul ist in Python unüblich, weil das in gewisser Weise das Modul als Organisationseinheit entwertet. Module sind dazu da thematisch zusammengehörige Klassen und Funktionen zu bündeln. Diese Aufgabe sollte man nicht auf Packages verlagern.

Was in anderen Programmiersprachen sinnvoll ist, sollte man nicht unbesehen auf andere Sprachen übertragen.

Bei C hat man keine Klassen, d.h. dort bündelt man thematisch zusammengehörige Funktionen in einer Übersetzungseinheit. Und bei C++ würde ich mal behaupten, dass man das macht, weil die Methoden in der Implementierungsdatei letztendlich auch wieder wie Funktionen einzeln aufgeführt werden, und in der Regel für die gleiche Semantik deutlich mehr Quelltextzeilen als in Python anfallen. So dass es dort tatsächlich etwas schwieriger ist in einer Datei mit Implementierungen für mehr als eine Klasse zu navigieren.
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

@BlackJack: Danke für die zügige Antwort :)
Eine Klasse pro Modul ist in Python unüblich, weil das in gewisser Weise das Modul als Organisationseinheit entwertet. Module sind dazu da thematisch zusammengehörige Klassen und Funktionen zu bündeln. Diese Aufgabe sollte man nicht auf Packages verlagern.
Gut, das heißt also für mich, dass ich alle Klassen in ein Skript schreibe und dieses dann einfach in das Main-Skript importiere? Momentan sieht meine Projektstruktur wie folgt aus:
  • - Package
    • - Main Skript
      - Skript1
      - Skript2
      - Skript3
      - Skript4
Ich würde also die vier Skripte mit den Klassen zu einem Skript zusammenfassen. Bisher dachte ich, es wäre so übersichtlicher zu lesen, aber dann werde ich das natürlich Python-konform abändern.
Was in anderen Programmiersprachen sinnvoll ist, sollte man nicht unbesehen auf andere Sprachen übertragen.
Da stimme ich dir zu ;)
Bei C hat man keine Klassen, d.h. dort bündelt man thematisch zusammengehörige Funktionen in einer Übersetzungseinheit. Und bei C++ würde ich mal behaupten, dass man das macht, weil die Methoden in der Implementierungsdatei letztendlich auch wieder wie Funktionen einzeln aufgeführt werden, und in der Regel für die gleiche Semantik deutlich mehr Quelltextzeilen als in Python anfallen. So dass es dort tatsächlich etwas schwieriger ist in einer Datei mit Implementierungen für mehr als eine Klasse zu navigieren.
Stimmt, in C gab es ja keine Klassen, sorry. In C++ konnte man so leichter arbeiten und hat meiner Meinung nach besser gesehen, wie die Zusammenhänge sind.
Ehrlich gesagt habe ich mich auch bereits gewundert, warum ein objektorientiertes Programmieren über mehrere Skripte in der Literatur nicht groß beschrieben wird (ich habe u.a. die Kapitel im Galileo Openbook dazu gelesen).
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

AnyOne hat geschrieben: Ehrlich gesagt habe ich mich auch bereits gewundert, warum ein objektorientiertes Programmieren über mehrere Skripte in der Literatur nicht groß beschrieben wird (ich habe u.a. die Kapitel im Galileo Openbook dazu gelesen).
Das Buch als gesamtes, aber insbesondere genau diese Kapitel sind besonders schlecht! Hier im Forum raten wir allen Anfängern dringend von diesem Buch ab. Einen Rant dazu findest Du hier.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

Hyperion hat geschrieben:
AnyOne hat geschrieben: Ehrlich gesagt habe ich mich auch bereits gewundert, warum ein objektorientiertes Programmieren über mehrere Skripte in der Literatur nicht groß beschrieben wird (ich habe u.a. die Kapitel im Galileo Openbook dazu gelesen).
Das Buch als gesamtes, aber insbesondere genau diese Kapitel sind besonders schlecht! Hier im Forum raten wir allen Anfängern dringend von diesem Buch ab. Einen Rant dazu findest Du hier.
Gut, das konnte ich natürlich vorher nicht wissen - danke für die Info! Ich werde mir den Rant dazu einmal durchlesen. Als weitere Literatur habe ich noch "Programming Python" von Mark Lutz aus dem "O'Reilly Media" Verlag zur Verfügung. Ist dieses besser?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

AnyOne hat geschrieben: Als weitere Literatur habe ich noch "Programming Python" von Mark Lutz aus dem "O'Reilly Media" Verlag zur Verfügung. Ist dieses besser?
Schlechter geht ja kaum :twisted: Wirklich gute deutsche Literatur gibt es zum Thema Python eher nicht. Aber ohne Englisch kommt man eh nicht weit beim Programmieren - insofern hab keine Scheu vor einem englischem Buch. Da gibt es ja auch einiges im Web; mehr dazu findest Du mittels SuFu oder im wiki :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Bliebe noch eine Frage.
AnyOne hat geschrieben:Nun möchte ich in meinem Main-Skript ein Objekt der Klasse D erstellen und somit die Funktionen der anderen drei Klassen abarbeiten lassen
Was hat Klasse D mit seinen Kindern zu schaffen?
Die Abhängigkeit darf eigentlich gar nicht auftreten.
BlackJack

@AnyOne: Grundsätzlich bleibt bei mir immer noch die Frage warum `Skript4` die anderen Module überhaupt importieren muss? Wenn da wirklich nur jeweils die Klassen drin sind, warum muss die Basisklasse dann ihre Unterklassen kennen? Das klingt komisch.

Wenn man zirkuläre Abhängigkeiten bei Modulen hat, gibt es in der Regel zwei verschiedene Wege diese aufzulösen. Module zusammenfassen, oder den Teil der die Abhängigkeiten zu beiden Modulen hat, in ein eigenes Modul auslagern. Je nach dem was mehr Sinn macht.

Was sollte man grossartig zu objektorientierter Programmierung über mehrere Module (Skripte ist hier irgendwie der falsche Begriff) beschreiben. Neben den technischen Aspekten — Quelltextdatei beschreibt Modul, Verzeichnis mit `__init__`-Modul macht ein Package aus, absolute und relative Importe — bleibt doch eigentlich nur zu sagen, dass ein Modul thematisch zusammengehörende Klassen und Funktionen zusammenfasst und Packages thematisch zusammengehörende Module zusammenfassen. Wenn ein Modul zu lang wird, kann man es aufteilen. Wobei man dabei auf eine sinnvolle Teilung Wert legen sollte. Also nicht nach dem Muster man hat 10 Klassen und verschiebt die jetzt willkürlich 5:5 auf zwei Module.

Ich würde noch den Tipp geben mehrmodulige (gibt's das Wort?) Programme in ein Package zu stecken, damit man den obersten Namensraum sauber hält und nicht so darauf achten muss ob es schon andere Module mit den jeweiligen Namen gibt, mit denen man kollidieren könnte. Da man aus einem Modul ganz einfach ein Package machen kann, macht es nichts wenn man mit einem Modul startet und erst im Verlauf merkt, dass man mehr als eines braucht.
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

Sirius3 hat geschrieben:Bliebe noch eine Frage.
AnyOne hat geschrieben:Nun möchte ich in meinem Main-Skript ein Objekt der Klasse D erstellen und somit die Funktionen der anderen drei Klassen abarbeiten lassen
Was hat Klasse D mit seinen Kindern zu schaffen?
Die Abhängigkeit darf eigentlich gar nicht auftreten.
Ich merke grad selbst wie schwachsinnig das eigentlich ist :D Ich wollte, dass die Klasse in Skript4 die Daten bekommt und Funktionen bereitstellt, die die Unterklassen benötigen. Dann dachte ich, ich könnte die Unterklassen in der Basisklasse aufrufen. Aber wenn ich jetzt so darüber nachdenke, so merke ich, dass das keinen Sinn macht.

@Hyperion: Das ist ein englisches Buch ;)

@BlackJack: Stimmt, das ist wirklich komisch. Danke für die Erklärung, so langsam wird mir klar wie es funktioniert
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

Irgendwie klappt das mit der OOP noch nicht so ganz -.-

Ich habe jetzt die Klassen alle in ein Modul geschrieben. Nun möchte ich eine "Elternklasse", in welcher vier Konstanten geschrieben werden. Auch soll diese eine Funktion beinhalten, welche alle anderen Klassen benutzen können. Dann würde diese Elternklasse ja wie folgt aussehen:

Code: Alles auswählen

class Parent:
    def __init__(self, A, B, C, D):
        self.__A = A
        self.__B = B
        self.__C = C
        self.__D = D

    def getA(self):
        return self.__A

    def getB(self):
        # ...

    def Funktion(self, A, B):
        # ...
Nun steht direkt darunter die nächste Klasse (wenn ich das in den vorherigen Posts richtig verstanden habe):

Code: Alles auswählen

class Base(Parent):
    def __init__(self, A, B, C, D, E)
        Parent.__init__(self, A, B, C, D)
        self.__E = E

        self.__A = Parent.getA()
        #...
Wäre der Aufruf so korrekt? Ich habe es nach diesem Muster nun in meinem Programm gemacht. Es wird der Konstruktor der Parent-Klasse aufgerufen, leider kommt das Programm dann nicht weiter. Was mache ich hier falsch?

Vielen Dank & viele Grüße
AnyOne
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Vergiss erstmal dass es ``__`` überhaupt gibt, das macht alles kompliziert und nur begrenzt viel besser. Das ist kein Private-Mechanismus und wenn du das weglässt braucht du auch keine Getter und Setter schreiben.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

Leonidas hat geschrieben:Vergiss erstmal dass es ``__`` überhaupt gibt, das macht alles kompliziert und nur begrenzt viel besser. Das ist kein Private-Mechanismus und wenn du das weglässt braucht du auch keine Getter und Setter schreiben.
Ok, also haben dann meine "Unterklassen" auf jeden Fall zugriff auf die Konstanten der Elternklasse? Sorry, zwei Jahre C und C++ hinterlassen ihre Spuren :)
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

AnyOne hat geschrieben:Ok, also haben dann meine "Unterklassen" auf jeden Fall zugriff auf die Konstanten der Elternklasse? Sorry, zwei Jahre C und C++ hinterlassen ihre Spuren :)
Du kannst den Zugriff nicht völlig zunageln. In Python gehen wir davon aus, dass es sich beim Entwickler um einen fachlich vorgebildeten Menschen handelt der weiß wann er nichts anfassen sollte, aber die Freiheit hat, gegebenenfalls doch einzugreifen wenn es nötig ist. In anderen Sprachen ist das mit der fachlichen Qualifikation wohl nicht ... ähmmm ... also was ich eigentlich sagen wollte war, dass in Python ein führender Unterstrich dazu verwendet wird um eine private Variable anzudeuten. Zwei führende Unterstriche verwursten dann noch den Namen der Klasse mit in das Attribut. Hier gilt: wenn du nicht weißt wofür es gut ist, dann brauchst du es nicht. :-) Bei Bedarf erklären wir es natürlich gerne.

Den Effekt auf die interne Namensvergabe kannst du dir mit folgendem kleinen Code ansehen.

Code: Alles auswählen

class Foo(object):
    def __init__(self):
        self.a = None
        self._b = None
        self.__c = None

print(Foo().__dict__)
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Noch ein paar Anmerkungen:
Alle Klassen werden üblicherweise von object abgeleitet.
Getter und Setter sind unüblich, statt dessen gibt es das Gegenteil: property
Elternfunktionen werden mit super aufgerufen:

Code: Alles auswählen

class Parent(object):
    def __init__(self, A, B, C, D):
        self.A = A
        self.B = B
        self.C = C
        self.D = D

    def Funktion(self, A, B):
        # ...

class Base(Parent):
    def __init__(self, A, B, C, D, E)
        super(Base,self).__init__(A, B, C, D)
        self.E = E
Grüße
Sirius
BlackJack

@AnyOne: Was mich jetzt noch ein wenig verwirrt ist der Begriff Konstante. Du gebrauchst den anscheinend anders als er üblicherweise verwendet wird, denn in Deinem Beispiel sind keine Konstanten zu sehen.
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

@/me: Danke für den Beispielcode, jetzt wird mir der Unterschied bewusst und das wird auch direkt geändert. Ich werde in Zukunft beim Programmieren von Python davon ausgehen, dass das Programm von Leuten verwendet wird, die nicht darin rumpfuschen ;)

@Siruis3: Das heißt also, wenn ich eine Klasse ableite und darin die "super" Funktion verwende, dann weiß der Interpreter direkt, dass damit die Elternklasse gemeint ist? Interessant das ich davon bisher leider nichts in der Literatur gefunden habe, scheint ja wirklich viel Käse auf dem Markt zu geben -.-

@BlackJack: Ich denke das trug wirklich zur Verwirrung bei! Ich wollte damit lediglich andeuten, dass diese Werte zu Beginn einmal vom Nutzer eingegeben werden sollen (es wird später noch eine GUI geben, welche das abfragt) und dann keine Veränderung in der Laufzeit mehr erfahren sollen. Deswegen - so lernte ich es eben im Studium mit C++ - wollte ich diese kapseln.

Ich danke euch allen nochmals sehr für eure Hilfsbereitschaft! Das ist wirklich ein tolles Forum! :D
BlackJack

Bevor man `super()` verwendet, sollte man es IMHO verstanden haben und welche Konsequenzen sich daraus ergeben: Python's Super is nifty, but you can't use it.

Ich bleibe bei einem einfachen ``Parent.__init__(A, B, C, D)`` und hatte bisher auch noch keinen Fall wo ich die ganze Komplexität von `super()` gebraucht hätte.
AnyOne
User
Beiträge: 18
Registriert: Donnerstag 25. Oktober 2012, 15:57
Wohnort: Aschaffenburg

BlackJack hat geschrieben:Bevor man `super()` verwendet, sollte man es IMHO verstanden haben und welche Konsequenzen sich daraus ergeben: Python's Super is nifty, but you can't use it.

Ich bleibe bei einem einfachen ``Parent.__init__(A, B, C, D)`` und hatte bisher auch noch keinen Fall wo ich die ganze Komplexität von `super()` gebraucht hätte.
Gut, so habe ich es bisher auch immer verwendet und werde dann wohl auch dabei bleiben. Ich möchte mir nicht noch mehr Probleme einfangen..
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@BlackJack: Raymond Hettinger findet super super.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@pillmuncher: Ja den Artikel kenne ich, bis zu dem hatte ich Hettinger mindestens für einen Halbgott gehalten. Die Idee bei allen Methoden immer auch **kwargs entgegen nehmen zu müssen und alle Methoden fortan nur noch mit Schlüsselwort-Argumenten aufrufen zu müssen, finde ich so alles andere als super. Bei der Adapterklasse habe ich mich dann gefragt ob er auf Drogen ist.
Antworten