Skip to main content

Codequalität mit Linting in Python verbessern

Artikel von

Keshav Malik

feature python linting

12. Oktober 2022

0 Min. Lesezeit

Python ist eine wachsende Programmiersprache. Während sie sich weiterentwickelt und immer mehr Möglichkeiten bietet, wächst auch die Zahl der verfügbaren Tools und Entwicklungsstrategien für die Arbeit mit Python. Ein Verfahren, das immer beliebter wird, ist Linting – also die Überprüfung von Code auf potenzielle Probleme. Beim Linting werden Fehler in unserem Code markiert, damit wir ungewöhnliche Programmierpraktiken korrigieren können, die zu Problemen führen könnten.

Linting wird durchgeführt, während der Quellcode geschrieben wird, also bevor er kompiliert wird. Anders gesagt: Linting ist eine Prüfung vor dem Build, auch „statische Codeanalyse“ genannt. Wenn wir unseren Code regelmäßig per Linting überprüfen, bleibt er im gesamten Code und in der Codebasis konsistent. So sinkt die Wahrscheinlichkeit, dass aus kleinen Fehlern nach der Ausführung des Codes komplexe Probleme werden.

Viele Entwicklerinnen und Entwickler verwenden kein Linting, weil sie keinen zusätzlichen Nutzen darin sehen – schließlich verhindert Linting keine Bugs. Diese Sichtweise unterschätzt jedoch, welchen Beitrag Linting zur Verbesserung der Codequalität leisten kann.

In diesem praxisorientierten Artikel erfahren Sie, wie schnell und einfach sich kurze Linting-Prüfungen in Python mit Pylint durchführen lassen – einem der beliebtesten Linting-Tools. Außerdem sehen wir uns an, wie Linting dabei helfen kann, den PEP8-Code-Styleguide einzuhalten.

Voraussetzungen

Stellen Sie vor dem Start sicher, dass Folgendes vorhanden ist:

  • Python und pip sind auf Ihrem Rechner installiert

  • Grundkenntnisse im Umgang mit Befehlszeilenschnittstellen (CLIs)

  • Kenntnisse der Python-Konzepte, etwa Funktionen und Klassen

Beachten Sie außerdem: Die hier gezeigten Befehle sind zwar mit Linux- und macOS-basierten Systemen kompatibel, bei der Verwendung von Windows sollten Sie jedoch vorsichtig sein.

Python-Code linten

Bevor wir uns ansehen, wie Sie einen Linter in Python verwenden, richten wir zunächst eine Verzeichnisstruktur und eine virtuelle Umgebung ein.

Unsere Umgebung einrichten

Erstellen Sie zunächst ein Projektverzeichnis. In diesem Tutorial nennen wir es pylint-demo.

$ mkdir pylint-demo

$ cd pylint-demo

Richten Sie als Nächstes eine virtuelle Umgebung ein. Dadurch werden die Abhängigkeiten unseres Projekts isoliert und Konflikte mit anderen Projekten vermieden.

$ pip install pipenv

$ pipenv shell

Ihre Eingabeaufforderung sollte etwa so aussehen: (pylint-demo) $. Das zeigt an, dass die virtuelle Umgebung aktiv ist.

Installieren Sie bei aktiver virtueller Umgebung den Linter mit folgendem Befehl:

$ pipenv install pylint

Nun können wir den Linter mit dem Befehl pylint ausführen. Um sicherzustellen, dass Pylint erfolgreich installiert wurde, führen Sie folgenden Befehl aus:

$ pylint –help

Erste Schritte mit Linting

Schreiben wir ein einfaches Python-Programm und lassen Pylint darauf laufen, um zu sehen, wie es funktioniert. Erstellen Sie eine Datei namens main.py und fügen Sie den folgenden Code ein:

def is_number_even(num):
    return "Even" if num % 2 == 0 else "Odd"

num = 5
print(f"The number {num} is {is_number_even(num)}")

Im obigen Code haben wir eine Funktion hinzugefügt, die prüft, ob eine Zahl gerade oder ungerade ist. Um diesen Code mit Pylint auf Fehler zu überprüfen, verwenden wir den folgenden Befehl:

$ pylint <<file_name>>
$ pylint main.py

Die Ausgabe von Pylint sieht folgendermaßen aus:

************* Module main
main.py:12:0: C0304: Final newline missing (missing-final-newline)
main.py:1:0: C0114: Missing module docstring (missing-module-docstring)
main.py:8:0: C0116: Missing function or method docstring (missing-function-docstring)
main.py:8:19: W0621: Redefining name 'num' from outer scope (line 11) (redefined-outer-name)
main.py:11:0: C0103: Constant name "num" doesn't conform to UPPER_CASE naming style (invalid-name)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

Im Code finden wir mehrere leicht verständliche Probleme, die jeweils durch einen Code wie C0304 gekennzeichnet sind. Pylint ordnet jedem Fehler einen Buchstabencode zu, um Schweregrad und Art des Problems zu unterscheiden. Es gibt fünf verschiedene Fehlerkategorien:

  • C: Konvention (bei Verstößen gegen Code-Konventionen)

  • R: Refactoring (bei Problemen im Zusammenhang mit Code Smells und Refactoring)

  • W: Warnung (bei Problemen auf Programmierebene, die keine Fehler sind)

  • E: Fehler (bei Problemen auf Programmierebene, die Fehler darstellen)

  • F: Schwerwiegend (bei ernsten Problemen, die die Ausführung von Pylint beendet haben)

Außerdem bewertet Pylint unseren Code anhand der Anzahl der vorhandenen Fehler mit einer Punktzahl von bis zu 10.

In unserem Beispiel handelt es sich bei allen Fehlercodes bis auf einen um Konventionsfehler; der einzige andere Fehler ist eine Warnung. Um diese Probleme zu beheben, ändern wir unseren Code an einigen Stellen und führen Pylint erneut aus. So sehen wir, welche Punktzahl unser Code erreicht.

""" File contains various function to under Pylint """

def is_number_even(num):
    """Function to check if number is even or odd"""
    return "Even" if num % 2 == 0 else "Odd"

NUM = 5
print(f"The number {NUM} is {is_number_even(NUM)}")

In diesem Code haben wir einen Modul- und einen Funktions-Docstring sowie eine neue Zeile am Ende hinzugefügt und die Variable aus dem obigen Code umbenannt. Bei einer erneuten Ausführung von Pylint erhalten wir die Bestnote 10/10 – ohne Beanstandungen.

Pylint für eine einzelne Datei ausführen

Da wir nun besser wissen, wie Pylint funktioniert, sehen wir uns ein weiteres Beispiel an. Geben Sie den folgenden Code ein:

""" File contains various function to under Pylint """

class animal:
  def __init__(self, name):
    self.name = name

obj1 = animal("Horse", 21)
print(obj1.name)

In diesem Codeausschnitt gibt es eine einfache Klasse namens animal und ein Objekt dieser Klasse namens obj1. Führen wir nun Pylint für diesen Code aus.

************* Module main
main.py:4:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
main.py:5:0: W0311: Bad indentation. Found 4 spaces, expected 8 (bad-indentation)
main.py:3:0: C0115: Missing class docstring (missing-class-docstring)
main.py:3:0: C0103: Class name "animal" doesn't conform to PascalCase naming style (invalid-name)
main.py:3:0: R0903: Too few public methods (0/2) (too-few-public-methods)
main.py:7:7: E1121: Too many positional arguments for constructor call (too-many-function-args)

Diesmal gibt es zwar keine Probleme mit der Codequalität, dafür sind jedoch schwerwiegendere Fehler aufgetreten. Nachdem die Probleme markiert wurden, versuchen wir, sie mit dem folgenden Code zu beheben:

""" File contains various function to under Pylint """

class Animal:
    "Animal Class"
    def __init__(self, name):
        self.name = name

obj1 = Animal("John")
print(obj1.name)

Führen Sie anschließend Pylint erneut aus. Nachdem wir den Klassennamen von animal in Animal geändert, der Klasse einen Docstring hinzugefügt, unerwünschte Argumente aus Funktionsaufrufen entfernt und die Einrückung korrigiert haben, sind fast alle Fehler in unserem Code behoben. Einer ist jedoch noch übrig:

************* Module main
main.py:3:0: R0903: Too few public methods (0/2) (too-few-public-methods)

Sehen wir uns an, wie wir diesen letzten Fehler beheben können. Pylint meldet, dass wir nicht über zwei oder mehr öffentliche Methoden verfügen. Es ist jedoch sehr wahrscheinlich, dass unser Code tatsächlich nicht über zwei oder mehr öffentliche Methoden verfügt. Wie lässt sich das Problem also beheben?

In einem solchen Fall können wir Python-Kommentare verwenden, um diese Probleme zu unterdrücken. Die Syntax dafür lautet:

# pylint: disable=<<issue_name>>

So sieht der Code dann aus:

""" File contains various function to under Pylint """

# pylint: disable=too-few-public-methods
class Animal:
    "Animal Class"
    def __init__(self, name):
        self.name = name

obj1 = Animal("John")
print(obj1.name)

Wenn wir uns jetzt die Pylint-Ausgabe ansehen, stellen wir fest, dass das Problem behoben ist.

Pylint für ein Verzeichnis ausführen

Wir haben gesehen, wie Pylint für eine einzelne Datei ausgeführt wird. Bei der Arbeit an einem Projekt haben wir jedoch nicht nur diese eine Datei, die wir prüfen müssen. Stattdessen müssen wir unser Verzeichnis linten.

Um Pylint für das gesamte Verzeichnis auszuführen, verwenden Sie folgenden Befehl:

$ pylint <<name_of_directory>>

Um zu sehen, wie das Linting eines Verzeichnisses funktioniert, erstellen wir zwei weitere Dateien und fügen Code hinzu.

$ mkdir src; cd src
$ touch helpers.py config.py __init__.py

Verschieben Sie die Datei main.py in das Verzeichnis src und fügen Sie den folgenden Code in die jeweiligen Dateien ein:

<<main.py>>
""" File contains various function to under Pylint """

from helpers import connect_db
from config import DB_USER, DB_PASS

is_connected = connect_db(DB_USER, DB_PASS)

if is_connected:
    print("Connected to DB")
else:
    print("Failed to connect to DB")

<<helpers.py>>
def connect_db(user, password):
    """Dummy function to connect to DB"""
    if user is None or password is None:
        return False
    return True

<<config.py>>
DB_USER = "root"
DB_PASS = "toor"

Im Verzeichnis src befinden sich drei Dateien: main.py, helpers.py und config.py. In main.py befindet sich eine einfache Funktion, die ausgibt, ob eine Verbindung zur Datenbank besteht. helpers.py enthält eine einfache Hilfsfunktion zum Herstellen der Datenbankverbindung. Die Datei config.py enthält den Benutzernamen und das Passwort für die Datenbank.

Führen wir Pylint nun mit folgendem Befehl aus dem Stammverzeichnis für das gesamte Verzeichnis aus:

$ pylint src

Der Befehl gibt Folgendes aus:

************* Module src.config
src/config.py:2:0: C0304: Final newline missing (missing-final-newline)
src/config.py:1:0: C0114: Missing module docstring (missing-module-docstring)
************* Module src.main
src/main.py:11:0: C0304: Final newline missing (missing-final-newline)
src/main.py:3:0: E0401: Unable to import 'helpers' (import-error)
src/main.py:4:0: E0401: Unable to import 'config' (import-error)
************* Module src.helpers
src/helpers.py:6:0: C0304: Final newline missing (missing-final-newline)
src/helpers.py:1:0: C0114: Missing module docstring (missing-module-docstring)

Wie wir sehen, zeigt Pylint die Ausgaben für die verschiedenen Dateien an. Sie werden durch *** und den Modulnamen voneinander getrennt. Um die Probleme zu beheben, müssen wir folgende Änderungen vornehmen:

  • Fügen Sie am Ende jeder Datei eine neue Zeile hinzu.

  • Fügen Sie jeder Datei und jeder Funktion einen Docstring hinzu.

  • Ändern Sie die import-Anweisung von helpers import connect_db in .helpers import connect_db.

Nachdem wir diese Probleme behoben haben, tritt ein weiteres auf: Die Variable is_connected muss großgeschrieben werden. Wir können entweder den Variablennamen ändern oder die Warnung unterdrücken, um diesen Fehler zu beheben.

Warnungen unterdrücken

Wahrscheinlich müssen Sie beim Linten Ihres Python-Codes mehrere Warnungen anpassen oder unterdrücken. Jedes Mal einen Kommentar hinzuzufügen, wäre nicht sinnvoll. Anstatt Warnungen einzeln zu unterdrücken, können Sie eine .rc-Datei erstellen, um das Verhalten von Pylint anzupassen und die Warnung direkt für das gesamte Projekt über die .rc-Datei zu unterdrücken.

Erstellen Sie eine solche Datei mit folgendem Befehl:

$ pylint --generate-rcfile > pylint.rc

Besserer und sichererer Code dank Linting

Linting in Python prüft den Quellcode bereits während des Schreibens und markiert Fehler, bevor der Code ausgeführt wird. Sie können Pylint auch in Editoren einbinden, um Linting-Ergebnisse in Echtzeit anzuzeigen.

Linting behebt Bugs zwar nicht automatisch, doch durch den konsequenten Einsatz lässt sich eine hohe Codequalität sicherstellen. Manche Entwicklerinnen und Entwickler halten Linting für Zeitverschwendung. Tatsächlich ist es jedoch äußerst effektiv, kleine Probleme zu erkennen, bevor daraus größere werden.

In diesem Artikel haben wir uns angesehen, wie Linting und die Umsetzung von Pylints Empfehlungen unseren Beispielcode verbessert haben. Außerdem hilft dieser Prozess ganz nebenbei dabei, den PEP8-Styleguide einzuhalten. Jetzt, da Sie Linting in Ihren Projekten einsetzen können, haben Sie die Möglichkeit, die vielen verfügbaren Linting-Tools zu erkunden und herauszufinden, welches am besten zu Ihrem Ansatz für die Python-Entwicklung passt und ihn optimal ergänzt.

Starten Sie mit Capture the Flag

Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.

Gepostet in: