Hallo!
Ich habe folgendes Problem:
Ich nutze zur Zeit die BioSQL-Python-Skripte (http://biopython.org/wiki/BioSQL) um Sequenzdaten in MySQL abzuspeichern. Leider reicht mir der Funktionsumfang nicht mehr aus und ich möchte die Skripte erweitern. Ich habe mir alle vier Skripte im source code angesehen und weiß, wo in welcher Klasse ich eine Methode hinzufügen möchte.
Nur wie mache ich das korrekt? Ich könnte die Originalskripte ergänzen, also direkt darin herumpfuschen. Am liebsten wäre mir jedoch, wenn ich die Originale laden/importieren könnte, und dann meine zusätzliche Methode hinzufüge.
Ich dachte zuerst daran, einfach ein neues Objekt von den bisherigen abzuleiten (Vererbung). Jedoch geht das nicht so schön, weil es wiederum von vielen anderen Objekten geladen wird, die ich dann auch alle ändern müsste. Was macht man da? Holzhammermethode? Ich denke, das ist ein ganz generelles Problem.
BioSQL erweitern, wie? Vererbung?
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Was sind denn die vier Scripte? Ich hatte auf den ersten Blick das ganze eher als Modul gesehen? Wenn Du Klassen um Funktionalität erweitern willst, musst Du von diesen erben.RedSharky hat geschrieben:Ich habe mir alle vier Skripte im source code angesehen und weiß, wo in welcher Klasse ich eine Methode hinzufügen möchte.
Na, genauso macht man es ja auch Du importierst Dir aus den Modulen die gewünschten Funktionen / Klassen usw. und bastelst Dir dann darauf aufbauend ein Script.Nur wie mache ich das korrekt? Ich könnte die Originalskripte ergänzen, also direkt darin herumpfuschen. Am liebsten wäre mir jedoch, wenn ich die Originale laden/importieren könnte, und dann meine zusätzliche Methode hinzufüge.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
Na ja, dass man das normalerweise so machen sollte, ist mir klar. Nur ist es hier sehr viel komplizierter als in den einfachen Beispielen. Wenn ich in den Sourcen herumpfuschen würde, wäre ich in zehn Minuten fertig.Hyperion hat geschrieben:Ich habe mir alle vier Skripte im source code angesehen und weiß, wo in welcher
Na, genauso macht man es ja auch Du importierst Dir aus den Modulen die gewünschten Funktionen / Klassen usw. und bastelst Dir dann darauf aufbauend ein Script.
Nur zermartere ich mir schon den ganzen Nachmittag das Hirn, wie ich das mit abgeleiteten Klassen hinbekommen könnte.
Ich möchte im BioSQL-Modul Loader.py in der Klasse DatabaseLoader eine Methode überschreiben und eine hinzufügen. Diese Klasse wird aber wiederum von diversen anderen Klassen/Methoden aus dem Modul BioSeqDatabase.py verwendet.
Wenn ich jetzt also eine neue Klasse von DatabaseLoader ableite, etwa DatabaseLoader2, dann müsste ich ja an diversen Stellen angeben, dass nicht die ursrüngiche Klasse, sondern eben die Neue verwendet werden soll. Dann müsste ich aber natürlich auch von diesen Klassen neue Klassen ableiten usw. Das gibt eine Kettenreaktion!
Wie verhindert man, dass das passiert?
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Hm... in dem Falle fällt mir nichts einfaches ein, das zu umgehen!
Wobei ich mich aber dennoch frage: Bist Du Dir auch sicher, dass das der richtige Punkt zum Ansetzen der Problemlösung ist? Das ganze klingt ja nach ziemlichen "Innereien" des Pakets. Also würde das in der Tat zu einem API-Bruch führen. Normalerweise sollte so etwas nur in Aussnahmefällen nötig sein. Also evtl. verrätst Du mal ein wenig deutlicher, wieso Du da in diese Tiefen eindringen musst?
Wobei ich mich aber dennoch frage: Bist Du Dir auch sicher, dass das der richtige Punkt zum Ansetzen der Problemlösung ist? Das ganze klingt ja nach ziemlichen "Innereien" des Pakets. Also würde das in der Tat zu einem API-Bruch führen. Normalerweise sollte so etwas nur in Aussnahmefällen nötig sein. Also evtl. verrätst Du mal ein wenig deutlicher, wieso Du da in diese Tiefen eindringen musst?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
Die Klasse DatabaseLoader aus dem Modul Loader.py speichert die Daten in MySQL ab. Damit ist es sowas wie eine Basisklasse und steht am Anfang der Nahrungs-/Vererbungskette. Ich möchte jetzt aber zusätzliche Informationen zu jedem Nukleotid abspeichern (in Form von fastq-sanger, ist ein nur String). Deshalb muss ich nun dort (in DatabaseLoader) neben die Methode zur Sequenzspeicherung nun noch eine Methode schreiben, die auch noch den neuen String abspeichert. Soweit ganz einfach.
Wenn ich nun aber eine neue Klasse ableite, muss ich aber auch dafür sorgen, dass diese auch verwendet wird - und nicht die alte. Deshalb muss man doch den Klassennamen überall dort ändern, wo der alte verwendet wird. Und wenn man nicht in den Originaldateien rumpfuschen möchte, muss man wiederum neue Klassen und Methoden ableiten. Das geht dann immer so weiter. Das kann es doch nicht sein! Da macht man ja dann noch mehr kaputt. Wenn man keine so grundlegende Klasse ändert, hat man das Problem natürlich nicht.
Oder hab ich da was nicht verstanden?
Man müsste eine Klasse richtig ersetzen und nicht nur ableiten oder so.
---
Edit:
Ein API-Bruch ist das nicht, denn BioSQL soll nur Objekte aus BioPython abspeichern, die genau diese Informationen standardmäßig enthalten. Nur BioSQL ist noch nicht so weit und das möchte ich ändern.
Wenn ich nun aber eine neue Klasse ableite, muss ich aber auch dafür sorgen, dass diese auch verwendet wird - und nicht die alte. Deshalb muss man doch den Klassennamen überall dort ändern, wo der alte verwendet wird. Und wenn man nicht in den Originaldateien rumpfuschen möchte, muss man wiederum neue Klassen und Methoden ableiten. Das geht dann immer so weiter. Das kann es doch nicht sein! Da macht man ja dann noch mehr kaputt. Wenn man keine so grundlegende Klasse ändert, hat man das Problem natürlich nicht.
Oder hab ich da was nicht verstanden?
Man müsste eine Klasse richtig ersetzen und nicht nur ableiten oder so.
---
Edit:
Ein API-Bruch ist das nicht, denn BioSQL soll nur Objekte aus BioPython abspeichern, die genau diese Informationen standardmäßig enthalten. Nur BioSQL ist noch nicht so weit und das möchte ich ändern.
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Also anscheinend ist also Dein Datenmodell nicht kompatibel zu vorgesehen Modell von diesem BioSQL-Modul? Ohne genauere Kenntnisse des Moduls und dessen API kann man da schwer eine Lösung vorschlagen. Sollte so etwas ein gängiger Fall sein, sollte das Datenbankmodell ja irgendwie erweiterbar sein. Wobei ich mich dann grad frage, wo genau in die DB Du da etwas speichern willst? Ich meine dafür muss es ja ein Attribut oder gar eine Tabelle geben - wenn ja, wird es doch auch eine Möglichkeit von der API-Seite geben, auf diese zuzugreifen?RedSharky hat geschrieben:Die Klasse DatabaseLoader aus dem Modul Loader.py speichert die Daten in MySQL ab. Damit ist es sowas wie eine Basisklasse und steht am Anfang der Nahrungs-/Vererbungskette. Ich möchte jetzt aber zusätzliche Informationen zu jedem Nukleotid abspeichern (in Form von fastq-sanger, ist ein nur String). Deshalb muss ich nun dort (in DatabaseLoader) neben die Methode zur Sequenzspeicherung nun noch eine Methode schreiben, die auch noch den neuen String abspeichert. Soweit ganz einfach.
Also so allgemein kann ich Dir da nicht weiterhelfen; einen brauchbaren Link zur Doku konnte ich auf die schnelle auch nicht finden. Zudem muss man sich wohl doch stark in die Materie einarbeiten, um zu kapieren, wie das eine mit dem anderen zusammenspielt.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
BioSQL speichert nur in ein paar vorgegebene Tabellen. Wenn man aber eine zusätzliche Tablelle für die neuen Daten anlegt, dann sollte man kompatibel bleiben, da BioSQL davon garnichts mitbekommt.
Aber so speziell ist das Thema auch nicht - eher grundlegend.
Ich verweise mal auf folgendes Schema aus dem Beitrag 'Vererbung (Programmierung)' auf Wikipedia (http://de.wikipedia.org/wiki/Vererbung_ ... mierung%29):
http://de.wikipedia.org/w/index.php?tit ... 0628083741
Im Grunde genommen möchte ich nur wissen, wie man Änderungen an einer Basisklasse vornimmt, in diesem Fall Fahrzeug, ohne alles mit dem Hintern einzureißen bzw. an jede abgeleitete Klasse Hand anlegen zu müssen.
Aber so speziell ist das Thema auch nicht - eher grundlegend.
Ich verweise mal auf folgendes Schema aus dem Beitrag 'Vererbung (Programmierung)' auf Wikipedia (http://de.wikipedia.org/wiki/Vererbung_ ... mierung%29):
http://de.wikipedia.org/w/index.php?tit ... 0628083741
Im Grunde genommen möchte ich nur wissen, wie man Änderungen an einer Basisklasse vornimmt, in diesem Fall Fahrzeug, ohne alles mit dem Hintern einzureißen bzw. an jede abgeleitete Klasse Hand anlegen zu müssen.
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Wenn Du von Fahrzeug ableitest, dann hast Du eine neue Klasse, die Du ab sofort benutzen kannst. Damit bereits extistierende Module diese Klasse nutzen, musst Du das in diesen anpassen. Woher sollen die auch wissen, dass sie jetzt PKW benutzen sollen?
Der einzige Weg drum herum ist es, einen Wrapper zu schreiben. Du schreibst also Deine eigene Klasse "Fahrzeug" in einem neuen Modul, tauscht Dein selbst erstelltes Modul gegen das Original aus und "schleifst" alle nicht geänderten Objekte einfach durch. Allerdings ist das natürlich auch aufwendig. Im Zweifel ist das Manipulieren des Orginialcodes an einer kleinen Stelle einfacher zu realisieren. Da muss man bei Updates lediglich diese Passagen erneut ergänzen.
Wirklich toll ist aber keine dieser Vorschläge! Will man eine API-Änmderung, so muss man sich am besten die Mühe machen, diese manuell an den gewünschten Stellen umzubauen.
Der einzige Weg drum herum ist es, einen Wrapper zu schreiben. Du schreibst also Deine eigene Klasse "Fahrzeug" in einem neuen Modul, tauscht Dein selbst erstelltes Modul gegen das Original aus und "schleifst" alle nicht geänderten Objekte einfach durch. Allerdings ist das natürlich auch aufwendig. Im Zweifel ist das Manipulieren des Orginialcodes an einer kleinen Stelle einfacher zu realisieren. Da muss man bei Updates lediglich diese Passagen erneut ergänzen.
Wirklich toll ist aber keine dieser Vorschläge! Will man eine API-Änmderung, so muss man sich am besten die Mühe machen, diese manuell an den gewünschten Stellen umzubauen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
Hmm, API-mäßig soll nichts verändert werden. Es sollen nur die Daten, die schon im Datenmodell vohanden sind, auch tatsächlich abgespeichert werden. Eigentlich sagt man nur: Speicher mir ein SeqRecord-Objekt (Sequenz mit Namen, Länge, Code, Urheber usw.) ab. Das macht BioSQL, vergisst dabei aber die von mir erwähnten Informationen.
Das mit dem Wrapper habe ich nicht verstanden. Wenn ich eine eigene Klasse 'Fahrzeug' in einem eigenen Modul erzeuge, dann muss ich doch auch in den darauf zugreifenden Modulen dafür sorgen, dass das neue Modul richtig importiert wird. Damit müsste ich zumindest im Originalcode 'from xyz import Fahrzeug' umbiegen. Damit hätte man aber wieder Hand an den Originalcode gelegt, was ja zu vermeiden war.
Das mit dem Wrapper habe ich nicht verstanden. Wenn ich eine eigene Klasse 'Fahrzeug' in einem eigenen Modul erzeuge, dann muss ich doch auch in den darauf zugreifenden Modulen dafür sorgen, dass das neue Modul richtig importiert wird. Damit müsste ich zumindest im Originalcode 'from xyz import Fahrzeug' umbiegen. Damit hätte man aber wieder Hand an den Originalcode gelegt, was ja zu vermeiden war.
Hat sonst noch jemand eine gute Idee wie man in einem bestehenden Projekt eine Basisklasse verändern kann?
Lasst uns folgedes Beispiel nehmen:
http://de.wikipedia.org/w/index.php?tit ... 0628083741
Sagen wir, man möchte bei der Klasse Fahrzeug die Methode 'Blinken' nachrüsten, weil die irgendwie vergessen wurde. Ansonsten möchte man nichts ändern und das komplette Paket weiter nutzen. Wie würde das am saubersten und einfachsten gehen. Kurz gesagt, wie macht man es richtig?
Lasst uns folgedes Beispiel nehmen:
http://de.wikipedia.org/w/index.php?tit ... 0628083741
Sagen wir, man möchte bei der Klasse Fahrzeug die Methode 'Blinken' nachrüsten, weil die irgendwie vergessen wurde. Ansonsten möchte man nichts ändern und das komplette Paket weiter nutzen. Wie würde das am saubersten und einfachsten gehen. Kurz gesagt, wie macht man es richtig?
@RedSharky: Wenn man die API vom originalen, "fremden" Code nicht ändern kann, will, oder darf, dann geht das einfach nicht.
Man könnte einiges bis zu einem gewissen Grad mit "monkey patching" erreichen, also die bestehende Klasse `Fahrzeug` importieren und das Klassenobjekt zur Laufzeit verändern. Aber das ist unsauber und auch gefährlich. So etwas kann man machen um Fehler in Fremdbibliotheken zu flicken bis die Autoren dazu kommen ihre Bibliothek zu aktualisieren, aber dauerhaft Funktionalität injizieren ist unschön.
Da muss man einfach an den Quelltext ran und dem `Fahrzeug` das Blinken beibringen. Wenn man das sauber implementiert, kann man es dem `Fahrzeug`-Hersteller als Patch schicken, damit er das in die Bibliothek für alle einbauen kann.
Man könnte einiges bis zu einem gewissen Grad mit "monkey patching" erreichen, also die bestehende Klasse `Fahrzeug` importieren und das Klassenobjekt zur Laufzeit verändern. Aber das ist unsauber und auch gefährlich. So etwas kann man machen um Fehler in Fremdbibliotheken zu flicken bis die Autoren dazu kommen ihre Bibliothek zu aktualisieren, aber dauerhaft Funktionalität injizieren ist unschön.
Da muss man einfach an den Quelltext ran und dem `Fahrzeug` das Blinken beibringen. Wenn man das sauber implementiert, kann man es dem `Fahrzeug`-Hersteller als Patch schicken, damit er das in die Bibliothek für alle einbauen kann.
Ohne es genau durchdacht zu haben und deinen Ausführungen genau gefolgt zu sein nur ein schneller Gedanke:
Was wäre Du leitest nicht von DatabaseLoader ab, sondern schreibst eine Klasse und läst DatabaeLoader von dieser erben.
Dann müsstest Du dich nicht darum kümmern wer bissher sonst noch die Klasse nutzt und machst die Erweiterung eins drüber !?
Wenn die Schnittstellen sich weiterhin gleich verhalten wäre die einzige Änderung, in DatabaseLoader die Vererbung anzugeben.
Kann man das dann "Adoption" nennen ?
Vielleicht ist das aber auch Quatsch und ich habs nicht weitgenug durchdacht.
Gruß
Was wäre Du leitest nicht von DatabaseLoader ab, sondern schreibst eine Klasse und läst DatabaeLoader von dieser erben.
Dann müsstest Du dich nicht darum kümmern wer bissher sonst noch die Klasse nutzt und machst die Erweiterung eins drüber !?
Wenn die Schnittstellen sich weiterhin gleich verhalten wäre die einzige Änderung, in DatabaseLoader die Vererbung anzugeben.
Kann man das dann "Adoption" nennen ?
Vielleicht ist das aber auch Quatsch und ich habs nicht weitgenug durchdacht.
Gruß
Genau das möchte ich haben. Aber wie macht man das?ichisich hat geschrieben: und läst DatabaeLoader von dieser erben.
Es muss nämlich genau die alte DatabaseLoader ersetzt werden (mit der neuen), sonst hat man zwei verschiedene DatabaseLoader und das bringt dann nichts.
Naja Du schreibst eine Klasse
In den Instanzen von DatabaeLoader kannst Du jetzt die Methode neue_Methode nutzen. Aber wie ich das schreibe merke ich, dass das stark hinkt...
a) Du hast ja in der neuen DatabaeLoaders_neu_Eltern keinerlei info was in DatabaeLoader passiert und auch keinen Zugriff auf Variablen und Methoden daraus.
b) Zudem greifst Du halt durch die neue Vererbung in die Quelle von DatabaeLoader ein.
c) Würdest du in DatabaeLoaders_neu_Eltern eine Methode definieren die es in DatabaeLoader gibt würde sie halt überschrieben werden, also auch doof ...
Das geht nur wenn b) soweit in Ordnung ist und du nur Sachen machst die a) nicht Voraussetzen
Also so richtig gut erscheint mir mein Vorschlag nicht ...
Code: Alles auswählen
class DatabaeLoaders_neu_Eltern(object):
def __init__()
pass
def neue_Methode(self):
pass
# in der Klasse DatabaeLoader
class DatabaeLoader(DatabaeLoaders_neu_Eltern):
a) Du hast ja in der neuen DatabaeLoaders_neu_Eltern keinerlei info was in DatabaeLoader passiert und auch keinen Zugriff auf Variablen und Methoden daraus.
b) Zudem greifst Du halt durch die neue Vererbung in die Quelle von DatabaeLoader ein.
c) Würdest du in DatabaeLoaders_neu_Eltern eine Methode definieren die es in DatabaeLoader gibt würde sie halt überschrieben werden, also auch doof ...
Das geht nur wenn b) soweit in Ordnung ist und du nur Sachen machst die a) nicht Voraussetzen
Also so richtig gut erscheint mir mein Vorschlag nicht ...
Ich habe eine Lösung gefunden:
Code: Alles auswählen
class Fahrzeug (modulname.Fahrzeug): # Erzeugt eine neue Klasse aus der Alten
is_new = True;
modulname.Fahrzeug = Fahrzeug # Ersetzt die alte klasse mit der Neuen - Voila
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Was ist daran eine Lösung? Wieso benutzen die vorhandenen Scripte jetzt Deine neue Klasse? Magisches Wissen?RedSharky hat geschrieben:Ich habe eine Lösung gefunden:
Code: Alles auswählen
class Fahrzeug (modulname.Fahrzeug): # Erzeugt eine neue Klasse aus der Alten is_new = True; modulname.Fahrzeug = Fahrzeug # Ersetzt die alte klasse mit der Neuen - Voila
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Ja. Aber das funktioniert ja nur, wenn das im selben Script steht, welches dann ausgeführt werden soll?!? Und genau das wollte der OP ja ursprünglich vermeiden, damit er nicht zu viel Code anfassen muss.DasIch hat geschrieben:@Hyperion Die letzte Zeile hast du schon gesehen?
Diese Lösung nutzt monkey patching, genau dass wovon BlackJack in seinem Post abgeraten hat.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
Der Patch muss ausgeführt werden bevor die Klasse genutzt wird, dass ist etwas problematisch wenn andere Module from-importe Machen, diese Module müssen ebenfalls nach dem Patch importiert/ausgeführt werden. Allerdings funktioniert es durchaus interpreterweit.Hyperion hat geschrieben:Ja. Aber das funktioniert ja nur, wenn das im selben Script steht, welches dann ausgeführt werden soll?!?DasIch hat geschrieben:@Hyperion Die letzte Zeile hast du schon gesehen?
Diese Lösung nutzt monkey patching, genau dass wovon BlackJack in seinem Post abgeraten hat.