Ruff: Ein moderner Python-Linter für fehlerfreien und wartbaren Code


Linting ist wichtig, um sauberen und lesbaren Code zu schreiben, den Sie mit anderen teilen können. Ein Linter ist wie Ruff ein Tool, das Ihren Code analysiert und nach Fehlern, Stilproblemen und verdächtigen Konstrukten sucht. Mit Linting können Sie Probleme beheben und die Qualität Ihres Codes verbessern, bevor Sie Ihren Code festschreiben und mit anderen teilen.

Ruff ist ein moderner Linter, der extrem schnell ist und über eine einfache Benutzeroberfläche verfügt, die die Verwendung unkompliziert macht. Es soll auch ein direkter Ersatz für viele andere Linting- und Formatierungstools wie Flake8, isort und Black sein. Es entwickelt sich schnell zu einem der beliebtesten Python-Linters.

In diesem Tutorial erfahren Sie, wie Sie:

  • Installieren Sie Ruff
  • Überprüfen Sie Ihren Python-Code auf Fehler
  • Beheben Sie automatisch Ihre Flusenfehler
  • Verwenden Sie Ruff, um Ihren Code zu formatieren
  • Fügen Sie optionale Konfigurationen hinzu, um die Flusenleistung zu steigern

Um dieses Tutorial optimal nutzen zu können, sollten Sie mit virtuellen Umgebungen vertraut sein, Module von Drittanbietern installieren und mit der Verwendung des Terminals vertraut sein.

Ruff installieren

Nachdem Sie nun wissen, warum das Linting Ihres Codes wichtig ist und warum Ruff ein leistungsstarkes Tool für diese Aufgabe ist, ist es an der Zeit, es zu installieren. Zum Glück ist Ruff sofort einsatzbereit, sodass keine komplizierten Installationsanweisungen oder Konfigurationen erforderlich sind, um es zu verwenden.

Vorausgesetzt, Ihr Projekt ist bereits mit einer virtuellen Umgebung eingerichtet, können Sie Ruff auf folgende Weise installieren:

$ python -m pip install ruff

Zusätzlich zu pip können Sie Ruff auch mit Homebrew installieren, wenn Sie macOS oder Linux verwenden:

$ brew install ruff

Conda-Benutzer können Ruff mit conda-forge installieren:

$ conda install -c conda-forge ruff

Wenn Sie Arch, Alpine oder openSUSE Linux verwenden, können Sie auch die offiziellen Distributions-Repositorys verwenden. Spezifische Anweisungen finden Sie auf der Ruff-Installationsseite der offiziellen Dokumentation.

Wenn Sie außerdem möchten, dass Ruff für alle Ihre Projekte verfügbar ist, können Sie Ruff mit pipx installieren.

Sie können überprüfen, ob Ruff korrekt installiert wurde, indem Sie den Befehl ruff version verwenden:

$ ruff version
ruff 0.4.7

Damit der Befehl ruff in Ihrem PATH angezeigt wird, müssen Sie möglicherweise Ihre Terminalanwendung schließen und erneut öffnen oder eine neue Terminalsitzung starten.

Flusen Ihres Python-Codes

Während Linting dabei hilft, Ihren Code konsistent und fehlerfrei zu halten, garantiert es nicht, dass Ihr Code fehlerfrei ist. Das Finden der Fehler in Ihrem Code lässt sich am besten mit einem Debugger und angemessenen Tests bewältigen, die in diesem Tutorial nicht behandelt werden. In den nächsten Abschnitten erfahren Sie, wie Sie Ruff verwenden, um nach Fehlern zu suchen und Ihren Arbeitsablauf zu beschleunigen.

Auf Fehler prüfen

Der folgende Code ist ein einfaches Skript namens one_ring.py. Wenn Sie es ausführen, ruft es einen zufälligen Herr der Ringe-Charakternamen aus einem Tupel ab und teilt Ihnen mit, ob dieser Charakter die Last des Einen Rings< getragen hat. Dieser Code hat keinen wirklichen praktischen Nutzen und macht nur ein bisschen Spaß. Unabhängig von der Größe Ihrer Codebasis sind die Schritte dieselben:

import os
import random

CHARACTERS = ("Frodo", "Sam", "Merry", "Pippin", "Aragorn", "Legolas", "Gimli", "Boromir", "Gandalf", "Saruman", "Sauron")

def random_character():
    return random.choice(CHARACTERS)

def ring_bearer():
    return name in ("Frodo", "Sam")

if __name__ == "__main__":
    character = random_character()
    if ring_bearer(character):
        print(f"{character} is a ring bearer")
    else:
        print(f"{character} is not a ring bearer")

Wenn Sie genau hinschauen, haben Sie möglicherweise bereits einige Probleme mit diesem Code entdeckt. Wenn nicht, machen Sie sich keine Sorgen, Sie können Ruff verwenden, um sie alle zu finden.

Der grundlegendste Befehl der Ruff CLI (Befehlszeilenschnittstelle) ist check. Standardmäßig überprüft dieser Befehl alle Dateien im aktuellen Verzeichnis. In diesem Beispiel können Sie den Befehl check ohne Argumente ausführen. Wenn Sie check für den obigen Code ausführen, wird Folgendes ausgegeben:

$ ruff check
one_ring.py:1:8: F401 [*] `os` imported but unused
one_ring.py:10:12: F821 Undefined name `name`
Found 2 errors.
[*] 1 fixable with the `--fix` option.

Erfolg! Ruff hat zwei Fehler gefunden. Es zeigt nicht nur die Datei- und Zeilennummern der Fehler an, sondern gibt Ihnen auch Fehlercodes und -meldungen. Darüber hinaus erfahren Sie, dass einer der beiden Fehler behebbar ist. Großartig!

Sie können Ruff anweisen, Fehler zu beheben, indem Sie das Flag --fix anwenden. Folgendes passiert, wenn Sie dem Vorschlag folgen:

$ ruff check --fix
one_ring.py:9:12: F821 Undefined name `name`
Found 2 errors (1 fixed, 1 remaining).

Der nicht verwendete Import wurde nun behoben und diese Codezeile wurde aus one_ring.py entfernt. Der letzte dieser beiden Fehler kann nicht automatisch behoben werden. Das Problem in Zeile 9 ist für Sie vielleicht offensichtlich, aber vielleicht ist es das auch nicht.

Zum Glück gibt Ihnen Ruff den Fehlercode und eine Möglichkeit, ihn schnell nachzuschlagen, ohne die Dokumentation online durchsuchen zu müssen. Geben Sie den zweiten ruff-Befehl ein: rule.

Da Ruff den Fehlercode bereitstellt, können Sie ihn an den Befehl ruff Rule übergeben, um weitere Details zur Fehlermeldung anzuzeigen, einschließlich eines Codebeispiels:

$ ruff rule F821

Wenn Sie diesen Befehl ausführen, erhalten Sie weitere Details im Markdown-Format in Ihrem Terminal:

# undefined-name (F821)

Derived from the **PyFlakes** linter.

## What it does
Checks for uses of undefined names.

## Why is this bad?
An undefined name is likely to raise `NameError` at runtime.

## Example

```python
def double():
    return n * 2  # raises `NameError` if `n` is undefined when `double` is called
```

Use instead:

```python
def double(n):
    return n * 2
```

## References
- [Python documentation: Naming and binding](https://docs.python.org/3/reference/executionmodel.html#naming-and-binding)

Anhand des zusätzlichen Kontexts aus dem Fehlercode können Sie nun erkennen, dass der Beispielcode, den Sie zuvor gesehen haben, denselben Fehler gemacht hat. Die Variable name in Zeile 9 wurde nicht als Argument an die Funktionssignatur ring_bearer() übergeben. Hoppla!

Um diesen Fehler zu beheben, können Sie ring_bearer() so ändern, dass es das Argument name übernimmt:

# ...

def ring_bearer(name):
    return name in ("Frodo", "Sam")

Nachdem Sie nun diese kleine Änderung am Code vorgenommen haben, können Sie ruff check erneut ausführen, um zu sehen, ob er erfolgreich ist:

$ ruff check
All checks passed!

Großartig! Beide Fehler sind nun behoben und Ihr Code sollte wie folgt aussehen:

import random

CHARACTERS = ("Frodo", "Sam", "Merry", "Pippin", "Aragorn", "Legolas", "Gimli", "Boromir", "Gandalf", "Saruman", "Sauron")

def random_character():
    return random.choice(CHARACTERS)

def ring_bearer(name):
    return name in ("Frodo", "Sam")

if __name__ == "__main__":
    character = random_character()
    if ring_bearer(character):
        print(f"{character} is a ring bearer")
    else:
        print(f"{character} is not a ring bearer")

Es kann unpraktisch sein, jedes Mal ruff check ausführen zu müssen, wenn Sie Ihren Code ändern. Zum Glück hat Ruff eine Lösung. Im nächsten Abschnitt erfahren Sie, wie Sie Ihren Code kontinuierlich auf Fehler überprüfen können.

Beschleunigen Sie Ihren Arbeitsablauf

Wenn Sie aktiv am Code arbeiten, kann Ruff Ihren Arbeitsablauf noch weiter vereinfachen, indem es Sie während der Entwicklung über Fehler informiert. Dadurch wird der Gesamtprozess beschleunigt und Sie werden produktiver. Um beim Codieren kontinuierliches Linting zu erhalten, öffnen Sie ein neues Terminalfenster und übergeben Sie das Flag --watch an den Befehl check:

$ ruff check --watch

Nachdem Sie den obigen Befehl ausgeführt haben, sollte in Ihrem Terminal etwa Folgendes angezeigt werden:

[14:04:01 PM] Starting linter in watch mode...
[14:04:01 PM] Found 0 errors. Watching for file changes.

Ihr Code ist jetzt fehlerfrei. Oder doch? Im nächsten Abschnitt erfahren Sie, was Ruff nicht standardmäßig erfasst hat.

Weitere Fehler finden

Auch wenn die von Ruff gefundenen Fehler behoben wurden, muss der Code noch bereinigt werden. Es gibt noch ein paar weitere Probleme mit der Datei one_ring.py, die behoben werden könnten, um diesen Code noch sauberer und lesbarer zu machen. Das bemerkenswerteste Problem ist in Zeile 3. Das CHARACTERS-Tupel scheint zu lang zu sein und könnte besser lesbar gemacht werden.

Sie stellen sich vielleicht die Frage: Warum hat Ruff das nicht aufgegriffen? Das ist eine vollkommen berechtigte Frage. Ein Blick in die Dokumentation ergibt folgende Antwort:

Standardmäßig aktiviert Ruff die F-Regeln von Flake8 zusammen mit einer Teilmenge der E-Regeln und lässt alle Stilregeln weg, die sich mit der Verwendung eines Formatierers überschneiden, wie z. B. ruff Format oder Schwarz. (Quelle)

Standardmäßig wendet Ruff die Regel zur Überprüfung der Zeilenlänge nicht an. Sie können ihm jedoch mitteilen, welche zusätzlichen Regeln Sie ein- oder ausschließen möchten. Sie können mit dem Flag --select anfordern, alle E-Regeln oder eine bestimmte Regel einzubeziehen:

$ ruff check --select E
one_ring.py:4:89: E501 Line too long (122 > 88)
Found 1 error.

$ ruff check --select E501
one_ring.py:4:89: E501 Line too long (122 > 88)
Found 1 error.

Ah, Sie haben den zusätzlichen Fehler gefunden. Möglicherweise stellen Sie jedoch fest, dass es keinen Hinweis darauf gibt, dass die Zeilenlänge automatisch mit dem Flag --fix festgelegt werden kann. Machen Sie sich keine Sorgen, denn es gibt eine Möglichkeit, Formatierungsfehler in Ruff mit einem neuen Befehl zu beheben. Im nächsten Abschnitt erfahren Sie mehr über das Ruff-Format.

Formatieren Sie Ihren Python-Code

Standardmäßig verfügt Ruff über sinnvolle Formatierungsregeln und wurde als direkter Ersatz für Black konzipiert. Der Befehl format ist seit Ruff Version 0.1.2 verfügbar.

Genau wie der Befehl check akzeptiert der Befehl format optionale Argumente für einen Pfad zu einer einzelnen Datei oder einem einzelnen Verzeichnis. Da der Code in diesem Tutorial-Beispiel eine einzelne Datei ist, können Sie ihn ohne Argumente verwenden:

$ ruff format
1 file reformatted

Ihre one_ring.py-Datei sollte jetzt besser lesbar aussehen und eine einheitliche Formatierung aufweisen:

import random

CHARACTERS = (
    "Frodo",
    "Sam",
    "Merry",
    "Pippin",
    "Aragorn",
    "Legolas",
    "Gimli",
    "Boromir",
    "Gandalf",
    "Saruman",
    "Sauron",
)


def random_character():
    return random.choice(CHARACTERS)


def ring_bearer(name):
    return name in ("Frodo", "Sam")


if __name__ == "__main__":
    character = random_character()
    if ring_bearer(character):
        print(f"{character} is a ring bearer")
    else:
        print(f"{character} is not a ring bearer")

Wie Sie sehen können, wurde der vorherige Zeilenlängenfehler in Zeile 3 behoben. Und obwohl das Tupel mehr Zeilen einnimmt, ist es viel einfacher, die Liste der Charakternamen zu analysieren und zu lesen. Dies erleichtert Codeprüfern auch die Überprüfung von Änderungen, da die meisten Tools und Plattformen nur anzeigen, was sich genau im diff geändert hat, und nicht in der gesamten Datenstruktur.

Die nächste Änderung besteht darin, dass die Abstände zwischen Funktionen jetzt konsistent und PEP 8-konform sind, mit den empfohlenen zwei Leerzeichen zwischen Funktionen.

Die letzte Änderung, auch wenn sie unbedeutend erscheinen mag, besteht darin, dass Ruff den fehlenden Zeilenumbruch am Ende der Datei hinzugefügt hat.

Dies ist ein kurzer Code, der einfach zu formatieren war. Längere Codebasen erfordern möglicherweise viele Änderungen, die möglicherweise einige Funktionen beeinträchtigen könnten. Dies kommt jedoch selten vor, da Formatierer immer auf der sicheren Seite sind. Weitere Informationen zu unsicheren Fixes in Ruff finden Sie im Abschnitt zur Fixsicherheit in der Dokumentation von Ruff.

Wenn Sie sehen möchten, welche Änderungen vorgenommen werden, wenn Sie ruff format ausführen, können Sie es mit dem Flag --diff ausführen, um die vorgeschlagenen Änderungen anzuzeigen, bevor Sie sie vornehmen ihnen. Wenn Sie das Flag --diff ausgeführt hätten, bevor Sie ruff format ausgeführt hätten, hätten Sie diese Ausgabe gesehen:

--- one_ring.py
+++ one_ring.py
@@ -1,16 +1,31 @@
 import random


-CHARACTERS = ("Frodo", "Sam", "Merry", "Pippin", "Aragorn", "Legolas", "Gimli", "Boromir", "Gandalf", "Saruman", "Sauron")
+CHARACTERS = (
+    "Frodo",
+    "Sam",
+    "Merry",
+    "Pippin",
+    "Aragorn",
+    "Legolas",
+    "Gimli",
+    "Boromir",
+    "Gandalf",
+    "Saruman",
+    "Sauron",
+)
+

 def random_character():
     return random.choice(CHARACTERS)

+
 def ring_bearer(name):
     return name in ("Frodo", "Sam")

+
 if __name__ == "__main__":
     character = random_character()
     if ring_bearer(character):
         print(f"{character} is a ring bearer")
     else:
-        print(f"{character} is not a ring bearer")
\ No newline at end of file
+        print(f"{character} is not a ring bearer")

1 file would be reformatted

Dies ist möglicherweise alles, was Sie jemals zum Formatieren Ihres Codes benötigen. Es kann jedoch vorkommen, dass Sie eine andere Zeilenlänge bevorzugen oder bestimmte Regeln ein- oder ausschließen möchten. In diesen Situationen kann es zeitaufwändig sein, jedes Mal, wenn Sie Ihren Code linten möchten, alle erforderlichen Regeln in der Befehlszeile aufzulisten. Es muss einen besseren Weg geben!

Es gibt. Obwohl dies nicht erforderlich ist, kann Ruff hochgradig konfigurierbar sein. Im nächsten Abschnitt erhalten Sie einen kurzen Einblick in einige Konfigurationsgrundlagen.

Ruff konfigurieren

Wenn Sie eine größere Codebasis linten, mehrere Committer haben oder Ihr Erlebnis anpassen möchten, können Sie Ihre Konfiguration mit Ruff in einer TOML-Datei speichern. Genauer gesagt, eine ruff.toml-, .ruff.toml-Datei oder Ihre vorhandene pyproject.toml-Datei.

Wie bereits erwähnt, verfügt ruff über sinnvolle Standardeinstellungen. Diese Konfigurationen sind auf der Ruff-Konfigurationsseite dokumentiert, damit Sie sie lesen können. Die vollständige Liste der für Ihre Konfiguration verfügbaren Einstellungen ist gut dokumentiert. Hier ist ein Beispiel für eine einfache ruff.toml-Konfiguration, die Sie Ihrem Projekt hinzufügen können:

line-length = 88

[lint]
select = ["E501", "I"]

[format]
docstring-code-format = true
docstring-code-line-length = 72

Und hier ist das gleiche Beispiel im Format pyproject.toml. Die einzige Änderung besteht darin, dass Sie in jeden Tabellenkopf ein tool.ruff-Präfix einfügen müssen:

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E501", "I"]

[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = 72

In diesen Beispielen werden Ihnen einige neue Regeln auffallen. Genau wie zuvor haben Sie angegeben, dass Sie beim Linting mit ruff die Regel E501 einbeziehen möchten, die einen Fehler zurückgibt, wenn die Zeilenlänge größer als die ist Standardmäßig 88 Zeichen.

Zusätzlich zum Hinzufügen der E501-Regel zur Linting-Konfiguration haben Sie Ruff auch gebeten, alle I-Regeln hinzuzufügen. I-Regeln gelten nur für isort, ein anderes Paket, das Sie möglicherweise schon einmal zum Flusen und Formatieren Ihrer Python-import-Anweisungen verwendet haben. Mit dieser Konfiguration benötigen Sie isort und Black nicht mehr, um Ihren Code zu formatieren. Das bedeutet weniger zu verwaltende Tools und weniger Entwicklerabhängigkeiten.

In den Zeilen 6 bis 8 sehen Sie, dass Ruff Ihre Dokumentzeichenfolgen nun auf eine Länge von 72 Zeichen formatiert. Diese Zahl kann beliebig sein, und viele wählen möglicherweise 88 Zeichen, um der Länge der Codezeile zu entsprechen. Beachten Sie, dass Ruff Dokumentzeichenfolgen standardmäßig nicht formatiert.

Es stehen viele Linting- und Formatierungseinstellungen zur Verfügung. Daher empfiehlt es sich, durch die Liste der Einstellungen zu scrollen, um zu sehen, welche Sie Ihrer Ruff-Konfiguration hinzufügen möchten.

Wenn Sie bereits Erfahrung mit einem Linter haben, teilen Sie uns gerne Ihre bevorzugten Regeln und Anpassungen in den Kommentaren unten mit.

Nächste Schritte

Nachdem Sie nun erfahren haben, warum Sie einen Linter verwenden sollten und wie Ruff ein großartiges Tool ist, das Ihnen dabei hilft, sauberen, lesbaren und fehlerfreien Code zu erhalten, sollten Sie Ruff ausprobieren.

Wie oben erwähnt, gibt es eine Vielzahl von Konfigurationen, mit denen Sie Ihre Flusen auf die nächste Stufe heben können. Es gibt auch einige Integrationen, die Ihren Arbeitsablauf beschleunigen können, z. B. die VS Code-Erweiterung, das PyCharm-Plugin, den Pre-Commit-Hook und GitHub-Aktionen.

Abschluss

Ruff ist ein extrem schneller Python-Linter- und Codeformatierer, der Ihnen dabei helfen kann, die Qualität und Wartbarkeit Ihres Codes zu verbessern. In diesem Tutorial wurden die ersten Schritte mit Ruff erklärt, die wichtigsten Funktionen vorgestellt und gezeigt, wie leistungsstark es sein kann.

In diesem Tutorial haben Sie Folgendes gelernt:

  • Ruff installieren
  • Überprüfen Sie Ihren Python-Code auf Fehler
  • Beheben Sie automatisch Ihre Flusenfehler
  • Verwenden Sie Ruff, um Ihren Code zu formatieren
  • Fügen Sie optionale Konfigurationen hinzu, um die Flusenleistung zu steigern

Mit diesem neuen Tool in Ihrer Toolbox können Sie Ihren Code auf die nächste Stufe heben und sicherstellen, dass er professionell aussieht und, was noch wichtiger ist, fehlerfrei ist.