BioSQL erweitern, wie? Vererbung?

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.
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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.
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.
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.
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.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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.
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.
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?
Benutzeravatar
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?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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 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?

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
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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.
Benutzeravatar
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.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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.
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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?
BlackJack

@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.
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

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 ? :wink:

Vielleicht ist das aber auch Quatsch und ich habs nicht weitgenug durchdacht.

Gruß
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

ichisich hat geschrieben: und läst DatabaeLoader von dieser erben.
Genau das möchte ich haben. Aber wie macht man das?
Es muss nämlich genau die alte DatabaseLoader ersetzt werden (mit der neuen), sonst hat man zwei verschiedene DatabaseLoader und das bringt dann nichts.
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Naja Du schreibst eine Klasse

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):
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 ...
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

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
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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
Was ist daran eine Lösung? Wieso benutzen die vorhandenen Scripte jetzt Deine neue Klasse? Magisches Wissen? ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
RedSharky
User
Beiträge: 99
Registriert: Donnerstag 13. April 2006, 15:38

Bei mir funktioniert es. Sagen wir, weitere Skripte/Module erwarten modulname.Fahrzeug vor zufinden. Dann können sie das auch weiterhin, nur liegt dort nun die erweiterte Klasse.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

@Hyperion Die letzte Zeile hast du schon gesehen?

Diese Lösung nutzt monkey patching, genau dass wovon BlackJack in seinem Post abgeraten hat.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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.
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.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Hyperion hat geschrieben:
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.
Ja. Aber das funktioniert ja nur, wenn das im selben Script steht, welches dann ausgeführt werden soll?!?
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.
Antworten