Teil 1: Python-Programmcode validieren

Aus LinuxUser 02/2024

Teil 1: Python-Programmcode validieren

© Deklofenak, 123RF

Spürnase

Code muss funktionieren, bevor Sie ihn freigeben. Das stellen Sie mit schlauen Werkzeugen wie PyLint, Flake8 oder Black sicher.

Guten Python-Code schreiben

Teil 1: Python-Programmcode validieren

Teil 2: Code optimieren

Teil 3: Dokumentation prüfen

Guten, lesbaren und funktionierenden Programmcode zu schreiben, fällt nicht immer leicht. Skriptsprachen wie Ruby, Tcl, Perl und Python setzen die Schwelle jedoch sehr niedrig an und erlauben, innerhalb kürzester Zeit ein produktives Ergebnis zu erzielen. Nicht ohne Grund verteidigt Python jährlich wiederkehrend seine Position als beliebteste Programmiersprache [1].

Zu einem Projekt gehören eine Codeanalyse und Bewertung sowie regelmäßige, automatisierte Tests (Unit- und Integration-Tests [2]). Sie helfen, den Programmcode valide zu halten und identifizieren unbenutzte und optimierfähige Programmteile. Letzteres von Hand zu finden und zu bereinigen, gelingt häufig nur bei kleinem, selbstgeschriebenen Code und erweist sich als fehleranfällig. Im Teil 2 dieser kleinen Serie besprechen wir Werkzeuge, die uns dabei helfen, überflüssige Codezeilen aufzuspüren. In Teil 3 widmen wir uns der Dokumentation und prüfen darin Codebeispiele auf Korrektheit.

Hinter der Codeanalyse steht häufig der Wunsch nach einer Einschätzung, wie gut der Programmcode ist und welcher Zeitrahmen und welches Budget man für die weitere Entwicklung, Pflege/Wartung sowie Tests überhaupt einplanen muss. Als Basis der Abschätzung dienen unterschiedliche Softwaremetriken (siehe Kasten “Softwaremetriken”) sowie die Projektlaufzeit, Dokumentation und der Schulungsaufwand für die Nutzer.

Softwaremetriken

Metriken sind Kriterien zur Messbarkeit eines bestimmten Zustands. Bei Software fließt die Größe des Quellcode in Zeilen oder Zeichen mit ein, ebenso die Verständlichkeit und Lesbarkeit des Quellcodes. Zum Einsatz kommen als Verfahren:

  • Anzahl der Codezeilen, Lines of Code (LOC),
  • die Function-Point-Analyse (FPA) zur Aufwandsabschätzung in der Analysephase,
  • Constructive Cost Model (COCOMO) zur Errechnung von Projektkosten aus anderen Kennzahlen,
  • die zyklomatische Komplexität (nach McCabe) zur Komplexitätsbestimmung eines Programmmodules,
  • die Halstead-Metrik zur Implementierungsabschätzung in der Entwurfsphase,
  • Kontrollflussorientierte Metriken (Überdeckungstests) wie Anweisungsüberdeckung, Zweigüberdeckung, Pfadüberdeckung oder Bedingungsüberdeckung.

Die Ergebnisse der einzelnen Tests werden gewichtet und miteinander kombiniert, woraus sich dann das Gesamtergebnis errechnet.

Prägnant formuliert, kommt es bei der Analyse Ihres Programmcodes auf das Worauf, Womit und Wie an. Worauf umfasst dabei verschiedene Softwaremetriken sowie eine Überprüfung hinsichtlich stilistischer, logischer und analytischer Fehler im Code. Womit bezieht sich auf die Analyse durch spezielle Softwarewerkzeuge. Hinter Wie verbirgt sich die statistische und semantische Codeanalyse.

Den drei genannten Punkten gehen wir detaillierter nach und stellen dabei ausgewählte Werkzeuge vor, die Sie bei der Bewertung des Programmcodes unterstützen. Vielfach erstellen die Tools einen abstrakten Syntaxbaum (Abstract Syntax Tree, AST), um damit den Python-Code nach deklarierten, aber ungenutzten Variablen, Funktionen, Klassen und Methoden zu durchforsten. Einige der Werkzeuge führen dabei den Programmcode aus und bieten Zwischenergebnisse. Damit klärt sich nicht nur, ob der Programmcode syntaktisch korrekt ist, sondern auch, ob er das erwartete Ergebnis liefert.

Werkzeuge

Die Tabelle “Werkzeugübersicht zur Prüfung von Code” stellt die verfügbaren, unter einer freien Lizenz stehenden Werkzeuge und deren Funktion gegenüber. Vielfach ergeben sich dabei Überschneidungen in deren Funktionalität, sodass eine saubere Kategorisierung nicht immer gelingt. Wir gehen davon aus, dass Werkzeuge in unserer Auflistung fehlen, die wir noch nicht kennen, und freuen uns über diesbezügliche Ergänzungen. Weitere, ebenso kommerzielle Werkzeuge sammelt die Webseite Analysis Tools ein [3].

Aufgabe/Funktion

Werkzeug/Modul

Code auf Korrektheit prüfen

Pylint [4], PEP8Online [5], Pythonbuddy [6], Pychecker [7], Flake8 [8], Pylama [9], Pyflakes [10], Py_compile [11], Compileall [12], Ast [13], Prospector [14], Mypy [15], Ruff [16]

Code stilistisch prüfen

Pylint, PEP8, Flake8, Pylama, Pycodestyle [17], Prospector, Wemake-python-styleguide [18], Dlint [19]

Code gemäß PEP8 formatieren

Black [20], Autopep8 [21]

Ungenutzten Code finden

Vulture [22], Pyflakes, Coverage [23], Uncalled [24], Dead [25], Pychecker, Pycallgraph3 [26], Unimport [27], Pyanalyze [28]

Korrekt?

Den Schritt der Korrektheitsprüfung übernimmt der Python-Interpreter automatisch, bevor er daraus ausführbaren Bytecode erzeugt. Sie lösen ihn mithilfe der drei Python-Module Py_compile, Compileall und Ast selbst aus. Das erstgenannte Tool kann nur mit einem einzelnen Skript umgehen, Compileall und Cst hingegen kommen mit einem gesamten Verzeichnis voller Skripte zurecht. Die ersten beiden rufen Sie mit dem Parameter -m Modulname auf, beispielsweise für Py_compile und das Python-Skript liste.py so: python3 -m py_compile liste.py.

Abbildung 1 zeigt die Fehlermeldung, die Py_compile nach der Prüfung des Codes ausgibt. Gefunden hat das Modul hier eine fehlende schließende Klammer für eine Liste in der ersten Zeile des Skripts liste.py.

Abbildung 1: Die Syntaxprüfung durch das Modul Py_compile liefert eine Fehlermeldung zurück.

Abbildung 1: Die Syntaxprüfung durch das Modul Py_compile liefert eine Fehlermeldung zurück.

Das Modul Ast analysiert den Code und erstellt daraus einen abstrakten Syntaxbaum (AST). Erneut ließe sich ein Python-Einzeiler als Aufruf nutzen, weitaus besser funktioniert jedoch ein ausgeklügeltes Skript mit erweiterter Fehlerbehandlung (Listing 1). In den Zeilen 3 bis 5 entnimmt Python daraus zunächst den Namen des zu prüfenden Skripts aus dem Aufrufparameter in die Variable dateiname und liest danach den Dateiinhalt in die Variable quellcode ein.

In Zeile 6 setzen Sie die Variable valid auf den Wert True. Damit fungiert sie als Ergebnis, wobei erstmal davon ausgegangen wird, dass sich aus dem zu prüfenden Programmcode überhaupt ein fehlerfreier AST erzeugen lässt (Zeile 8). Gelingt das, erhalten Sie als Ausgabe ein schlichtes True. Geht etwas schief, indem ein Fehler auftaucht, erhält die Variable valid den Wert False. Anschließend wird der Stacktrace mittels der Funktion print_exc() aus dem Modul traceback ausgegeben (Zeile 11). Zudem erscheint am Ende die zusätzliche Ausgabe False (siehe Abbildung 2).

Listing 1

Syntaxprüfung durch Erstellung eines abstrakten Syntaxbaums

import ast, traceback, sys
dateiname = sys.argv[1]
with open(dateiname) as f:
quellcode = f.read()
valid = True
try:
    ast.parse(quellcode)
except SyntaxError:
     valid = False
     traceback.print_exc()
print(valid)

Abbildung 2: Schl&auml;gt die Syntaxpr&uuml;fung durch das Modul Ast fehl, lautet die Ausgabe am Ende <code>False</code>.

Abbildung 2: Schlägt die Syntaxprüfung durch das Modul Ast fehl, lautet die Ausgabe am Ende False.

Geht es um mehr als eine reine Syntaxprüfung, kommen andere Werkzeuge wie Pychecker, Pyflakes, Pylint, Pylama oder Prospector ins Spiel. In die Beurteilung des Codes fließen dann zusätzlich stilistische Tests und eine Komplexitätsprüfung mit ein.

Stylecheck

Python gibt Entwicklern sehr großen Freiraum und lässt ihnen bei der Nutzung von Paradigmen und Konzepten absolut die Wahl. Um jedoch für Programmcode eine gewisse Einheitlichkeit in der Schreibweise zu erreichen, nahm die Community bereits 2001 den Verbesserungsvorschlag Python Enhancement Proposal 8 (PEP8) [29] an. Er fällt relativ umfangreich aus: PEP8 begrenzt beispielsweise die Länge einer Codezeile auf 79 Zeichen und die eines Kommentars auf 72 Zeichen, gibt Empfehlungen für Zeilenumbrüche und Leerzeichen sowie für die Anordnung von Operatoren zur besseren Lesbarkeit in Funktionsaufrufen.

Diverse Werkzeuge – PEP8, PEP8 Online, Pylama, Pylint, Pyflakes, Flake8, Pythonbuddy und auch Pycodestyle (als Nachfolger von PEP8) – prüfen den Python-Code gegen die PEP8-Konvention. Als Ergebnis liefern die Tools eine Übersicht, welche Programmkomponenten den Stilvorgaben aus PEP8 bereits entsprechen, und welche noch nicht. Letztere müssen Sie dementsprechend überprüfen.

Listing 2 zeigt ein Skript zur Berechnung der Fakultät. Es enthält dazu die rekursive Funktion fakultaet() (Zeilen 1 bis 7). Als Parameter nimmt diese Funktion einen Zahlenwert entgegen und errechnet daraus über einen rekursiven Funktionsaufruf den Wert für die Fakultät für den Zahlenwert. Zeile 9 enthält eine Zahlenliste aus den vier Werten 15, 4, -8 und 0. In den Zeilen 10 bis 15 durchläuft eine For-Schleife diese Zahlenliste, ruft für jeden Zahlenwert die zuvor definierte Funktion fakultaet() auf und gibt anschließend, sofern möglich, den berechneten Wert für die Fakultät aus, ansonsten eine Fehlermeldung. Nachfolgend werfen wir den Programmcode aus Listing 2 mehreren zuvor genannten Werkzeugen zur Analyse vor und werten aus, wie diese den Programmcode einstufen.

Listing 2

Berechnung der Fakultät

def fakultaet(zahl):
    if zahl < 1:
        return -1
    elif zahl == 1:
        return 1
    else:
        return zahl * fakultaet(zahl - 1)
zahlenliste = [15, 4, -8, 0]
for wert in zahlenliste:
    ergebnis = fakultaet(wert)
    if ergebnis == -1:
        print("Kann keine Fakultät für Werte < 1 berechnen")
    else:
        print("Fakultät für %i ist %i" % (wert,ergebnis))

PEP8 und PEP8Online

Die naheliegendsten Werkzeuge zur Beurteilung von Programmcode sind PEP8 für die Kommandozeile und das webbasierte Pendant PEP8Online. Zur Analyse ruft die webbasierte Variante im Hintergrund das Kommandozeilenwerkzeug auf – die Ergebnisse sind identisch und unterscheiden sich lediglich in ihrer Darstellung. Abbildung 3 zeigt die Einschätzung des Python-Codes aus Listing 2 durch PEP8Online.

Das Tool bemängelt hierbei nur zwei eher stilistische Schwachstellen: ein fehlendes Leerzeichen zwischen den beiden Aufrufparametern wert und ergebnis in Zeile 15 sowie ein fehlender Zeilenumbruch am Ende derselben Zeile. Ob der Kürze des Ergebnisses wiegt Sie das in Sicherheit, wirkt aber etwas mager und geht besser.

Abbildung 3: Die Bewertung durch PEP8Online f&ouml;rdert zwei kleinere Sch&ouml;nheitsfehler zutage.

Abbildung 3: Die Bewertung durch PEP8Online fördert zwei kleinere Schönheitsfehler zutage.

Pylint

Abbildung 4 und Abbildung 5 demonstrieren eine Evaluierung des Programmcodes mittels Pylint – jeweils mit dem Originalcode aus Listing 2 und nach der Umsetzung der Verbesserungsvorschläge. Die Ausgabe von Pylint umfasst Verbesserungsvorschläge aus unterschiedlichen Kategorien (Tabelle “Kategorien zur Beurteilung der Codequalität durch Pylint”) sowie am Ende einen Punktwert aus dem Bereich von 0 bis 10. Je höher der Wert liegt, umso größer fällt die Übereinstimmung mit PEP8 aus, und umso qualitativ höherwertiger ist der Programmcode einzuschätzen.

Rangierte der ursprüngliche Wert bei 6.67 Punkten, ließ sich mit wenigen Schritten ein Maximum von 10 Punkten erreichen. Die Schreibweise der Bedingung if-else in der Funktion vereinfachten wir. Das ermöglicht dem Python-Interpreter, daraus einfacheren Prozessorcode (“processor instructions”) zu generieren, indem intern weniger Sprunganweisungen und passende Labels benötigt werden. Weiterhin lassen sich die Paare aus den Zeilen 5 und 6 sowie 7 und 8 parallel ausführen und verringern die Laufzeit. Die Ergänzung um Dokumentation für das Modul und die Funktion mittels Docstrings (Zeilen 1 und 4) [30] sowie das Nutzen von f-Strings anstatt von C-artigen Platzhaltern (Zeile 17) brachten aufgrund besserer Lesbarkeit zusätzliche Punkte.

Abbildung 4: Auf Anhieb bewertet Pylint den Originalcode mit 6.67&nbsp;Punkten.

Abbildung 4: Auf Anhieb bewertet Pylint den Originalcode mit 6.67 Punkten.


Abbildung 5: Der auf der Basis der Vorschl&auml;ge von Pylint verbesserte Programmcode erzielt die Maximalpunktzahl&nbsp;10.

Abbildung 5: Der auf der Basis der Vorschläge von Pylint verbesserte Programmcode erzielt die Maximalpunktzahl 10.

Kategorie

Abkürzung

Beschreibung

Informational messages

I

Informationen und Anmerkungen (fließen nicht in die Bewertung ein)

Refactor messages

R

Code entspricht nicht den Gepflogenheiten

Convention messages

C

Code entspricht nicht dem Programmierstandard

Warning

W

Warnung zu Stilfehlern oder minimalen Programmschwächen

Error

E

Hinweis zu wichtigen Programmschwächen oder Fehlern

Fatal

F

Ernsthafte Fehler, die die weitere Ausführung verhindern

In früheren Veröffentlichungen von Pylint war eine grafische Oberfläche zu dem Kommandozeilenwerkzeug enthalten. Diese setzte auf TK auf und wird seit 2018 nicht mehr gepflegt. Ein äquivalenter Nachfolger zu Pylint-gui ist uns nicht bekannt. Programmierumgebungen wie Visual Studio Code bieten jedoch passende Module zur Erweiterung an [31].

Flake8 und Pylama

Flake8 und Pylama stechen aus der Liste der zuvor genannten Werkzeuge insofern hervor, dass sie mehrere einzelne Werkzeuge miteinander kombinieren. Beide nehmen Pyflakes und Ned Batchelders McCabe-Skript [32] als Grundlage. Flake8 ergänzt dazu noch Pycodestyle, Pylama hingegen PEP8, PEP257 und Pylint. Die damit erzielte Aussage zur Code-Komplexität fällt somit genauer aus als die der Einzelwerkzeuge für sich betrachtet.

Lassen wir unser bereits zuvor verbessertes Skript durch Flake8 und Pylama bewerten, finden beide mithilfe von Pycodestyle noch zwei stilistische Fehler. Außerdem bemängeln die Tools fehlende Leerzeilen nach den Zeilen 3 und 11 zur besseren Lesbarkeit des Python-Codes (siehe Abbildung 6).

Abbildung 6: Die Bewertung durch Flake8 und Pylama fallen wegen der Kombination mehrerer Tools genauer aus.

Abbildung 6: Die Bewertung durch Flake8 und Pylama fallen wegen der Kombination mehrerer Tools genauer aus.

Vorschläge umsetzen

Die bisher aufgezählten Werkzeuge vergleichen den Programmcode lediglich mit einer Stilfibel, ohne selbst Änderungen daran vorzunehmen. Weitaus invasiver operieren Black und Autopep8. Beide formatieren den Programmcode gemäß den Vorgaben von PEP8 auf der Basis eigener Codestile. Dabei verarbeiten die Tools komplette Dateien, nicht nur einzelne Ausschnitte. Beispielhaft nutzen wir nachfolgend Black.

Als Ausgangspunkt dient die Überprüfung, ob Black in unserem Code überhaupt Änderungen vornehmen würde. Dazu liefert das Werkzeug den Schalter --check mit. Mit dem zusätzlichen Schalter -t (Langform --target-version) stellt sich Black zudem auf eine bestimmte Python-Version ein, für die es den Code anpasst. Für Python 3.9 lautet die Angabe -t py39, für Python 3.12 -t py312. Abbildung 7 zeigt zunächst die Rückmeldung von Black für die Überprüfung, Abbildung 8 die tatsächliche Korrektur auf Python 3.9. In vorliegendem Fall halten sich die Änderungen in Grenzen, sprich: Unser Code ist bereits sehr gut. Black ergänzte lediglich noch zwei Leerzeilen zur besseren Lesbarkeit des Programmcodes.

Abbildung 7: Black meldet nach der Pr&uuml;fung auf m&ouml;gliche Korrekturen, dass es eine fehlerhafte Datei identifiziert hat.

Abbildung 7: Black meldet nach der Prüfung auf mögliche Korrekturen, dass es eine fehlerhafte Datei identifiziert hat.


Abbildung 8: Nachdem es Korrekturen ausgef&uuml;hrt hat, quittiert das Tool seine &Auml;nderungen.

Abbildung 8: Nachdem es Korrekturen ausgeführt hat, quittiert das Tool seine Änderungen.

Zusammenfassung und Fazit

In unseren Tests mit Programmcode aus verschiedenen Quellen waren die Änderungen mehr oder weniger stark, aber stets mit einem konsistenten Resultat. Aus unserer Sicht helfen Black und Autopep8 dabei, Programmcode unterschiedlicher Entwickler (an demselben Projekt) stilistisch zu vereinheitlichen. Über entsprechende Plugins integrieren Sie beide außerdem in IDEs und Editoren wie Emacs, Vim, Atom, Pycharm oder Visual Studio Code. Als Pre-commit [33] erfüllen die Werkzeuge ebenso zuverlässig ihre Funktion bei Git, beispielsweise in Kombination mit Flake8 [34].

Nicht mehr ignorieren lässt sich der Hype um künstliche Intelligenz und die Idee, damit Programmcode zu verbessern. Hier sticht Github Copilot [35] heraus, das Empfehlungen auf Basis von Programmcode aus anderen, öffentlich verfügbaren Github-Repositories gibt. Der Tenor zu den Ergebnissen der Anwendung von Github Copilot erscheint gemischt, aber überwiegend positiv, sofern es als Anregung und Inspiration aufgenommen wird.

Die Fragen, die sich für uns dabei stellen, betreffen Urheberschaft und Lizenz. Entlehnte Codefragmente aus anderen Repositories kennzeichnet Github Copilot nicht konsequent als solche [36], ebenso die Lizenz des vorgeschlagenen Fragments. Unklar ist außerdem, ob es sich um die korrekte Lizenz handelt – Programmcode kann mehrfach lizenziert sein, etwa unterschiedlich für kommerzielle und nicht-kommerzielle Nutzung. Davon hängt maßgeblich ab, ob die Empfehlung von Github Copilot im konkreten Fall überhaupt im eigenen Projekt zum Einsatz kommen darf. Ohne manuelle Überprüfung des Vorschlags lauert hier die Falle der Urheberrechts- und Lizenzverletzung.

Nun liegt uns validierter Code vor – eine große Verbesserung verglichen mit dem Ausgangszustand. Diese Situation nutzen wir, um den Code im Folgeschritt dahingehend noch zu überprüfen, ob dieser überflüssige Zeilen enthält. Im zweiten Teil dieser Artikelserie rücken wir Werkzeuge und Methoden in den Mittelpunkt, die Ihnen dabei helfen, den Code zu verschlanken. (csi)

Danksagung

Der Autor bedankt sich bei Saif du Plessis für seine Hilfe und Kritik bei der Vorbereitung des Artikels.

Infos

  1. Die beliebtesten Programmiersprachen 2021: https://www.netzwoche.ch/news/2021-08-30/das-sind-die-beliebtesten-programmiersprachen-2021

  2. Anthony Shaw: Getting Started With Testing in Python: https://realpython.com/python-testing/

  3. Analysis Tools: https://analysis-tools.dev/tag/python

  4. Pylint: https://pylint.org/

  5. PEP8Online: http://pep8online.com/

  6. Pythonbuddy: https://github.com/ethanchewy/PythonBuddy

  7. Pychecker: http://pychecker.sourceforge.net/

  8. Flake8: https://github.com/pycqa/flake8

  9. Pylama: https://github.com/klen/pylama

  10. Pyflakes: https://pypi.org/project/pyflakes/

  11. Pythonmodul Py_compile: https://docs.python.org/3/library/py_compile.html

  12. Pythonmodul Compileall: https://docs.python.org/3/library/compileall.html

  13. Pythonmodul Ast: https://docs.python.org/3/library/ast.html

  14. Prospector: https://pypi.org/project/prospector/

  15. Mypy: https://www.mypy-lang.org/

  16. Ruff: https://docs.astral.sh/ruff/

  17. Pycodestyle: https://pycodestyle.pycqa.org/en/latest/

  18. Wemake-python-styleguide: https://wemake-python-stylegui.de/

  19. Dlint: https://github.com/dlint-py/dlint

  20. Black: https://pypi.org/project/black/

  21. Autopep8: https://github.com/hhatto/autopep8

  22. Vulture: https://github.com/jendrikseipp/vulture

  23. Coverage: https://pypi.org/project/coverage/

  24. Uncalled: https://pypi.org/project/uncalled/

  25. Dead: https://pypi.org/project/dead/

  26. Pycallgraph3: https://pypi.org/project/pycallgraph3/

  27. Unimport: https://pypi.org/project/unimport/

  28. Pyanalyze: https://pypi.org/project/pyanalyze/

  29. Python Enhancement Proposal 8 – PEP-8-Style-Guide for Python Code: https://www.python.org/dev/peps/pep-0008/

  30. Aditya Sharma: Docstrings in Python Tutorial: https://www.datacamp.com/tutorial/docstrings-python

  31. Pylint-Erweiterung für Visual Studio Code: https://marketplace.visualstudio.com/items?itemName=ms-python.pylint

  32. McCabe-Skript: https://pypi.org/project/mccabe/

  33. Pre-Commit: https://pre-commit.com/

  34. Lj Miranda: Automate Python workflow using pre-commits: black and flake8: https://ljvmiranda921.github.io/notebook/2018/06/21/precommits-using-black-and-flake8/

  35. Github Copilot: https://github.com/features/copilot

  36. KI-Assistent Github Copilot will Code nicht klauen, sondern sauber referenzieren: https://www.heise.de/news/KI-Assistent-Github-Copilot-will-Code-nicht-klauen-sondern-sauber-referenzieren-9234532.html

  37. Axel Beckert, Frank Hofmann: Debian-Paketmanagement-Buch: https://dpmb.org/

  38. Frank Hofmann arbeitet meist von unterwegs aus als Entwickler, Trainer und Autor. Er gehört zu den Verfassern des Debian-Paketmanagement-Buchs [37].

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 02/2024 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben