Stell dir vor dein Team entwickelt ein Framework, aber die Sprache ist Python
von Max Bechtold„Und du machst jetzt Python?“
Diese Frage habe ich mehrfach während und nach einem Projekteinsatz gehört, in dem erstmals Python im Vordergrund stand. Die Leute klangen, wie wenn ihnen jemand erklärt, zu einer Fußwanderung durch Grönland aufbrechen zu wollen. Und zwar ohne Navi und nur mit einem Gaskocher und zwei Dosen Ravioli. Tatsächlich hat es sich erstmal nach Neuland angefühlt, mit Python zu arbeiten. In meinem ersten Python-Projekt bei einem Kunden war ich ein bisschen Prospektor in – aus meiner Sicht – unerschlossenem Terrain.
Hintergrund
Der Kunde möchte eine Low-Code-Plattform, auf der Domainexpert*innen selbst Apps entwickeln können. Da diese keine Softwareentwickler*innen sind, aber Erfahrung mit Python haben, sollte ein Framework bzw. APIs für die Apps ebenfalls in Python geschrieben werden. Damit, so das Ziel, würde für die Fachleute alles einheitlich wirken. Die Entwicklung war API-getrieben und natürlich sollte Open Source Software herangezogen werden. Alles sollte sich in bereits existierende Continuous Integration-Prozesse (CI) einfügen, so dass von vornherein der Anspruch bestand, zu modularisieren und „richtige“ Python-Projekte aufzusetzen. Aber geht das – und wie?
Build/Infrastruktur
Die saloppe Antwort: Alles geht, nichts muss in Python. Die Sprache lässt viele Freiheiten und gibt sehr wenig vor. Ein Beispiel: Während man sich bei Spring Boot per Webseite [1] das Projektsetup bequem zusammenklickt, versucht man in Python den Einstieg direkt in einer IDE oder stöbert erstmal nach Anleitungen im Netz. Vielleicht stößt man dabei auf den offiziellen „Packaging Guide“ [2]. Er stellt die einzelnen Build-Schritte für Python-Projekte vor und die verschiedenen Werkzeuge, die es dafür jeweils zur Auswahl gibt. Dort werden aber keine Maßnahmen zur Qualitätssicherung berücksichtigt wie Linting oder die verschiedenen Testebenen. In diese Lücke stößt tox [3], das sich zwar auf Testinstrumentierung fokussiert, aber auch um die Build-Schritte davor und danach bis zum Release kümmern kann. Das ist noch kein ganzheitliches Konzept wie bei Maven oder Gradle mit den Phasen bzw. Lifecycle Tasks, aber eben auch nicht mehr weit davon entfernt. Die Python-Dokumentation verweist oft auf verbreitete Tools in der Community, tox gehört aktuell nicht dazu. Wir haben uns für die Instrumentierung mit Gradle entschieden, weil das im Projektumfeld üblich war und dort bereits Tooling existierte.
Um in verschiedenen Projekten dieselbe Bibliothek in mehreren Versionen verwenden zu können, sieht Python virtuelle Umgebungen vor, die venvs („virtual environments“). Diese sind abgeleitet von einer installierten Python-Runtime. Venvs sind eine gute Idee, aber auch hier gibt es verschiedene Werkzeuge. Idealerweise wählt man eines, das hermetische Builds [4] unterstützt und in die eigene IDE integriert werden kann.
Als Analogie dafür, wie sich die ersten Wochen professioneller Python-Entwicklung angefühlt haben: Stell dir vor, du hast keinen gut sortierten Werkzeugkasten, sondern fängst mit einer leeren Kiste an und füllst sie aus dem Baumarkt. Nehme ich den einfachen Akkuschrauber oder einen mit LED-Licht und Drehmomentbegrenzung? Reicht nicht ein Schraubendreher? Und so weiter…
Wir hatten dennoch bald ein individuelles Bündel Tools für ein stabiles und wartbares Projekt-Setup geschnürt. Damit konnten wir regelmäßig neue Versionen in einer CI-Pipeline bauen und über ein internes PyPI-Repository (vgl. Maven Mirror) verteilen. Das Schaubild zeigt, wie unser Werkzeugkasten aufgebaut war. Meistens gab es mehr als eine Alternative zu dem jeweiligen Werkzeug, aber wir sahen während meiner Zeit im Projekt keinen Grund zu wechseln.
Coding
In Python geschriebener Code ist klar, einfach und verzichtet auf „Zeremonielles“. Im Vergleich zu z.B. Java hat Python etwas „Hemdsärmeliges“ an sich. Statt public/private
-Modifikatoren setzt man einen Unterstrich („_“) vor ein Element, um etwas als „internal“ zu kennzeichnen. Klammern als Trennzeichen sind nahezu nirgends notwendig, Semikolons spart man sich. Selbst Leerzeichen (oder Tabulatoren) werden nicht zum Spaß gesetzt – die Einrückung entspricht einem Code-Block. Obwohl „significant whitespace“ die Entwickler-Community spaltet, ist Python nicht die einzige Sprache, die die entsprechende Off-side-Regel [5] anwendet.
Algorithmischer Code schreibt sich mit dem funktionszentrierten Python besser als in anderen Sprachen. Filtern und Mappen von Daten lässt sich mit List comprehensions meistens kompakt darstellen. Wer möchte, kann sich mit dem Überladen von Operatoren auch DSLs basteln und damit den Code noch ausdrucksstärker machen.
Typisierung ist erst mal nicht gesetzt in Python. Anders als bei JavaScript, für das eigenständige Aufsätze mit Typsystem existieren (z.B. TypeScript, Dart), wurde Python aber um optionale Typannotationen erweitert, die nun Teil der Sprache sind [6]. Man tut sich einen Gefallen damit, durchgängig Typen zu verwenden, denn dann hilft die IDE, Typfehler zu vermeiden. Es gibt zudem Libraries, mit denen der Interpreter zur Laufzeit direkt Fehler wirft, anstatt Argumente eines unerwarteten Typs umzuwandeln und dann im Verlauf zu falschen Werten oder Ausführungspfaden zu gelangen (Python setzt stark auf Duck Typing [7]).
In der Community gilt der Anspruch, dass Python-Code „pythonic“ sein muss. Im Wesentlichen bedeutet das, sich an den Stil-Guide [8] zu halten und den „Zen of Python“ [9] zu kennen. Als Plausibilitätscheck fragt man sich: „Kann ich das noch einfacher schreiben?“ Diese Idee hat Überschneidung mit den Coding- und Designrichtlinien des Extreme Programming, man könnte auch sagen „clean code“ ist „pythonic“.
Für gute und langlebige Softwareprodukte sind Unittests unerlässlich, und da lässt Python einen nicht allein. Tests schreibt man einfach als Funktionen, nutzt Mocks und Fixtures direkt aus der Standard-Bibliothek und lässt gleich noch die Coverage berechnen. Ganz so einfach war es am Ende aber nicht, denn wir haben eine Linter-Regel definiert, damit Produktivcode nicht versehentlich auf Testcode zugreift (ein Nachteil von „alles geht in Python“).
Einschränkungen & Stärken
Nach einem Dreivierteljahr mit einem einzigen Anwendungsfall kann man keine abschließende Entscheidung für oder gegen Python treffen. Ein paar Einschränkungen und Stärken haben sich aber abgezeichnet und würden sich im nächsten Einsatz wahrscheinlich bestätigen. Die Grundlage dieses Artikels entstand in einem single-user/local-machine-Szenario, d.h. Nebenläufigkeit und Skalierbarkeit waren nicht von Bedeutung. Überhaupt gilt: „it depends“. In einem Projekt mit Machine Learning kommt man fast nicht an Python vorbei, bei einer Web-App für mobile Geräte findet man sicher mehr als eine besser geeignete Technologie.
Python setzt stark auf Konventionen, damit muss man sich arrangieren bzw. sich im Team einigen, inwieweit man das noch erweitert. Eine grundlegende Entscheidung ist das verwendete Paradigma, denn Python kann man nicht nur objektorientiert, sondern auch funktional einsetzen. Für ereignisgetriebenen Code, z.B. in einem Serverless-Szenario, kann das sinnvoll sein. Wir haben uns im Projekt in Klassen (Singletons bzw. Stateless) organisiert, auf Mehrfachvererbung aber verzichtet.
Die Flexibilität der Sprache hat ihren Preis. Wenn man stark modularisiert und auf APIs in Python setzt, muss man sich Gedanken machen, wie man sich oder vielmehr den Konsumenten eines Moduls vor der Verwendung von Interna schützt, denn auch „interne“ Methoden sind zugänglich. Auch das Typsystem ist noch nicht ganz ausentwickelt (z.B. kam der Selbstbezug von Klassen durch Self
erst mit Python 3.11 [10]).
Für den Unternehmenskontext spielt eine Rolle, dass Python erst dabei ist, hier Fuß zu fassen. Bei Stack Overflow kann man als Java-Entwickler*in eigentlich damit rechnen, dass man schnell eine gute Antwort auf das aktuelle Problem findet. Bei Python ist mein Eindruck, dass manche Fragen noch gar nicht gestellt wurden.
Der Knackpunkt der professionellen Softwareentwicklung ist das Tooling und dabei vor allem die Entwicklungsumgebung. Für die meisten Sprachen gibt es mehrere IDE-Anbieter und wenn nicht, dann mindestens eine Erweiterung für Visual Studio Code. In einem Training mit Python haben wir diesen Allrounder von Microsoft auch eingesetzt, wurden aber nicht glücklich. Vor allem fehlte die Unterstützung für Refactorings – mehr als Umbenennen und einfaches Extrahieren von Funktionen war nicht drin. Dann doch lieber JetBrains‘ PyCharm, das auch in der freien Community Edition viele Refactorings und Content Assists (Editierunterstützung) bietet. Auch dort stößt man aber bisweilen an Grenzen und fragt sich, wieso man diesen oder jenen Schritt in Zeiten von ChatGPT noch selbst ausführen muss.
Ich habe mir die Stärken für das Ende aufgehoben, denn deretwegen will man sich vielleicht mal mit Python beschäftigen. Schnelles Ausführen wie hier vermisse ich inzwischen bei Sprachen mit Compiler. Ich finde für alles Bibliotheken (Open Source oft mit liberaler Lizenz), meist mit guter Dokumentation und einsehbarem Bug Tracker. Ich schreibe schnell und einfach Tests und bei „untestbarem“ Code kann man immer noch patch
[11] einsetzen. Python selbst unterliegt einem offenen Entwicklungsprozess (einsehbar auf https://peps.python.org/) und pro Version werden Updates für fünf Jahre in Aussicht gestellt. Die Weiterentwicklung in der Python Software Foundation wird durch Großunternehmen (z.B. Google) gefördert.
Fazit
Ja, Python ist anders. Aber es ist auch nicht komplett verschieden von etablierten Tech-Stacks. Selbst wenn es für viele noch Neuland ist, kann man sich mit Erfahrungen aus anderen Bereichen der Softwareentwicklung orientieren und wird einen Weg finden.
Wer sich die Mühe macht und alle Plus- und Minuspunkte dieses Beitrags zusammenzählt merkt, dass mich Python nicht im Sturm erobert hat. Dennoch ist meine Einschätzung am Ende positiv. Es gibt noch Potenzial, vielleicht ist der vergleichsweise geringe Grad an Professionalisierung auch ein Henne-Ei-Problem. Der TIOBE-Index, den Python zum jetzigen Zeitpunkt wieder mal als populärste Sprache anführt, lässt auf eine rosige Zukunft hoffen. Auf jeden Fall hat es Spaß gemacht damit zu arbeiten. So wäre vielleicht die beste Antwort auf die anfängliche Frage:
„Ja, ich mache Python. Und du?“
Hinweis: Dieser Artikel fußt nicht nur auf dem beschriebenen Kundenprojekt, sondern auch auf einer internen Diskussionsrunde und Erfahrungsaustausch mit andrena-Kolleg*innen.
Quellen
[1] |
„Spring initializr“: https://start.spring.io/ |
[2] |
„Packaging Python Projects“: https://packaging.python.org/en/latest/tutorials/packaging-projects/ |
[3] |
„tox User Guide“: https://tox.wiki/en/latest/user_guide.html |
[4] |
„Hermeticity“: https://bazel.build/basics/hermeticity |
[5] |
„Off-side rule“: https://en.wikipedia.org/wiki/Off-side_rule |
[6] |
„PEP 484 – Type Hints“: https://peps.python.org/pep-0484/ |
[7] |
„Duck typing“: https://en.wikipedia.org/wiki/Duck_typing |
[8] |
„PEP 8 – Style Guide for Python Code“: https://peps.python.org/pep-0008/ |
[9] |
„PEP 20 – The Zen of Python“: https://peps.python.org/pep-0020/ |
[10] |
„PEP 673 – Self Type“: https://peps.python.org/pep-0673/ |
[11] |
„unittest.mock — mock object library“: https://docs.python.org/3/library/unittest.mock.html#patch |