Python und Inversion of Control

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
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hi,

praktiziert ihr in euren (größeren) Projekten IoC mit einem DI-Container - und wenn ja: Wie bildet ihr das ab?

Ich habe ein wenig mit einem DI-Container in Python experimentiert, dafür braucht man aber eigentlich eine sinnvolle Art von "Interfaces" (also in Python-Sprache: Den Hinweis, dass ich eine Ente erwarte, die quarken und fliegen kann). So wie ich das sehe, hat sich da jetzt mit der 3.5er einiges getan - habe da aber noch kein richtiges Bild vor Augen - oder stehe schlicht auf dem Schlauch.

TypeHinting auf konkrete Implementierungen macht ja auch keinen Sinn - und eigentlich würde ich die Abhängigkeiten meiner Applikation schon gerne zentral verwalten, ohne eine ServiceRegistry zu benutzen.

Kann gut sein, dass ich in den letzten Jahren etwas ent-pythonifiziert wurde - darum wollte ich mal eure Meinung dazu hören.

Danke schonmal!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Barabbas: Wie so vieles, was in der Java-Welt nötig ist, weil sie so eine starre Vererbungshierachie hat, und Funktionen keine First-Class-Objekte sind, ist auch das Inversion-of-Control-Pattern in seiner Reinform in Python wohl eher nicht anzutreffen. Vieles läßt sich viel eleganter funktional per Generator lösen, wo Java duzende Klassen stapelt. Daher ist es ja auch eher unüblich, explizit eine Ente zu erwarten.

Kannst Du eine Beispielimplementierung Deiner Container posten. Daran läßt sich doch leichter diskutieren.
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hi Sirius3,

mein Szenario ist eigentlich folgendes: Ich habe eine größere Applikation, die ich objektorientiert entwerfe. Was ich konkret möchte ist:

* Die jeweiligen Klassen sollen möglichst genau eine Aufgabe haben
* Es soll keine harte Bindung auf andere Klassen via import / Instanzierung geben
* Einzelne Klassen in der Applikationen sollen einfach austauschbar sein - etwa für Unittests oder damit andere Entwickler meine Applikation / Library leicht erweitern können, ohne in "meinem" SourceCode rumpfuschen zu müssen.

Der Container, den ich genutzt habe, ist dieser hier (nicht von mir): https://gist.github.com/intedinmamma/2385916 Das Problem ist weniger der Container selbst, als das Entwerfen einer Applikation, die in sich austauschbar ist. Mir ist schon klar, dass Interfaces etc. eher aus der Java-Welt stammen - aber in bspw. der PHP-Community hat man ja in den letzten Jahren sehr gute Erfahrungen gemacht, solche Muster auch in dynamische Sprachen zu adaptieren.

Ganz konkret lautet meine Frage vermutlich schlicht:

Wie schreibe ich meine Klasse so, dass sie eine Abhängigkeit auf eine andere Klasse hat, ohne auf die konkrete Implementierung dieser anderen Klasse zu dependen. Der Duck-Typing-Ansatz funktioniert zwar hier natürlich auch - aber ist ja bspw. in der IDE oder im SourceCode sehr schwer zu ersehen, welche realen Abhängigkeiten mein SourceCode hat, wenn es nicht irgendeine Art der Beschreibung (=Interfaces) gibt.

Der Container an sich ist mir also vll. gar nicht so wichtig, wie die Frage, wie ich in Python auf "Abstraktionen statt Konkretisierungen" depende.

Danke schonmal für deine Zeit!
BlackJack

@Barabbas: Den dusseligen Hype gibt's immer noch? :-) Ich fand das bei Java furchtbar als ich das benutzen musste weil die IDE plötzlich viel weniger nützlich war, da sie von diesen ”magischen” Abhängigkeiten nichts wusste und die Navigation im Code viel schlechter lief.

Ich habe da bei Python bisher noch keinen Sinn drin gesehen, also kein Problem was mit DI-Rahmenwerken gelöst wird, das man ohne nicht mit weniger Schreibarbeit lösen kann. Um mal einen Satz aus dem Wikipedia-Artikel zu zitieren: „Move calls to a factory or a service locator out of the client and into main and suddenly main makes a fairly good dependency injection container.“ Wobei mit `main` die Startfunktion `main()` gemeint ist.

Das Beispiel bei Github verstehe ich zum Beispiel nicht. Da wo das alles dem Container bekannt gemacht wird kann man das doch auch viel einfacher direkt zusammensetzen. Was bringt mir der Container? Achtung: Bei DI darf ich den nur an der einen Stelle benutzen wo alles zusammengebaut wird! Denn sonst wäre es nicht DI sondern das Service-Locator-Pattern.

So ein Container bringt etwas wenn man dadurch Arbeit spart, wenn man Beispielsweise den Umstand ausnutzt das eine statisch typisierte Sprache mit Interfaces so einem Container die Zuordnung fast frei Haus ermöglicht weil diese Informationen schon da sind und man sie sowieso schreiben muss. Bei Python müsste man dagegen erst zusätzlich solche Annotationen oder irgendwie eine Identifizieren in den Code bringen damit Client und Service zusammenfinden.
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hi BlackJack,

du treibst dich ja auch noch hier rum :)

Ja, DI ist tatsächlich nicht ganz unumstritten und ja auch nicht direkt IoC. Aber wenn ich meinen Code auf irgendeine Art unabhängig von der konkreten Implementierung machen möchte, muss ich doch irgendwie sagen können "Das ist mein WetterService und der hat als Abhängigkeit einen WetterDatenBereitsteller - das ist ein Objekt mit einer Methode "getWeatherFor(city, date)".

Die Duck-Typing Antwort ist ja vermutlich "ist egal, solange es eine Methode "getWeatherFor" hat, ist es für dich passend und du brauchst kein Interface. Aber dadurch wird mMn der Code schlechter lesbar, weil mein "WetterService" auf einen "WetterDatenBereitsteller" dependet - im SourceCode aber gar nicht erkennbar ist, was der macht.

Das ist gerade so die Frage, die ich mir stelle.



Daniel
BlackJack

@Barabbas: Naja so funktioniert „duck typing“ aber nun mal und das ist (noch) normal in Python. Wenn das für Dich unübersichtlich ist dann muss doch Python-Code insgesamt für Dich unübersichtlich sein. Und die Art das zu sagen was Du da in Anführugzeichen gesetzt hast ist genau das in Anführungszeichen als Docstring in den Quelltext an der entsprechenden Stelle zu schreiben und gegebenfalls ein Werkzeug wie Sphinx zu verwenden um eine Dokumentation zu erstellen.

Wenn da tatsächlich nur eine Methode von einem Objekt benötigt wird, dann würde ich da sogar direkt angeben das `WetterService` ein aufrufbares Objekt haben möchte das zu einer gegebenen Stadt und einem Datum das Wetter liefert. Denn sonst zwingt man den Benutzer dieser API am Ende noch nur deswegen eine Klasse zu implementieren weil die API unbedingt eine Klasse wollte. Wenn nur eine ”Funktion” gefordert ist, kann der Benutzer entscheiden ob er eine Funktion schreibt oder eine Klasse, davon dann ein Exemplar erstellt und die entsprechende Methode davon übergibt.

Bei Klassennamen wie `WetterDienst` (`WeatherService`) und `WetterDatenBereitsteller` (`WeatherDataProvider`), und der Schreibweise `getWeatherFor()` habe ich aber sowieso schon das Gefühl dass das kein Python ist. ;-)
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hi,

ja, die Namen waren jetzt nur so runtergeschrieben, javaesk und denglisch, ist klar :).

Die "eine Methode" in dem Unterobjekt ist ja auch nur ein Beispiel - aber ja, das zwinge ich erstmal mir selbst auf (was ja völlig ok ist) und später dem erweiternden Entwickler (der dafür im Austausch ein klares Interface erhält, das ich pflege und kompatibel halte).

Das ist auch alles nicht als "Bashing" gemeint. Ich habe nur bis 2012 viel Python gemacht, seitdem viel PHP und da enorm viel Anstrengung erlebt, gute Architektur umzusetzen (was mich überrascht hat). Und frage mich jetzt natürlich, ob ich das für Python adaptieren kann - was bei unterschiedlichen Sprachen mit unterschiedlichen Paradigmen natürlich immer schwer ist.

Ich denke, ich werde erstmal meine Refaktorierungen voran treiben, die nächsten Wochen, und euch dann nochmal konkret um Tipps bitten, statt hier ein Fass aufzumachen.

Danke für die Antworten so far!


Daniel
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Barabbas: was Du bei Deinen Refaktorierungsüberlegungen berücksichtigen solltest, ist, dass Klassen nur dort eingesetzt werden, wo es wirklich nötig ist. Wenn eine Funktion ausreichend ist, braucht es keine Klasse. Abstrakte Klassendefinitionen mit großen Vererbungshierarchien gibt es nicht. Parameter sollten nicht von einer bestimmten Klasse erben müssen.
Die Dokumentation beschreibt dann, welche Parameter eine übergebene Funktion haben sollte, oder welche Methoden das Objekt bereitstellen soll. In Java ist es ja dann oft auch so, dass ein riesiges abstaktes Klassenmodell erstellt wird, und zum Schluß gibt es dann genau eine konkrete Klasse, die das implementiert. Daher, bei allem was Du machst, denke an KISS. In Python ist es viel einfacher, nachträglich Komplexität hinzuzufügen, wenn es nötig wird, als in anderen Sprachen (dank callable Klassen oder Properties). Wenn Du also eine Indirektion jetzt nicht brauchst, wirst Du sie wahrscheinlich nie brauchen, ist es also auch nicht nötig, sie jetzt schon zu implementieren.
BlackJack

@Barabbas: Ich würde mich übrigens von einer API die ein Objekt mit *einer* erwarteten Methode haben möchte und für deren Implementierung aber eine Funktion ausreichen würde, nicht dazu zwingen lassen eine Klasse zu schreiben. Ich würde eine entsprechende Funktion mit dem erwarteten Namen schreiben und der doofen API dann das *Modul* übergeben in dem die Funktion definiert ist. :D Dann meckert mich mein Editor auch nicht an das ich eine Klasse mit zu wenig öffentlichen Methoden definiert habe.
Antworten