Lektion 22: ruff - Extremely Fast Python Linter
Tool-Lektion | 35 Minuten
Dieser Abschnitt zeigt dir Installation, Grundbefehle und erweiterte Konfiguration von ruff -- vom ersten Check bis zur vollstaendigen CI/CD-Integration.
Lernziele
- Metadata
- Verstehen warum dieses Tool wichtig ist
- Die wichtigsten Einsatzgebiete kennen
- Installation und grundlegende Nutzung beherrschen
- Production-ready Patterns anwenden
Lektion 22: ruff - Extremely Fast Python Linter
Metadata
- Kategorie: Fortgeschrittene Tools
- Schwierigkeit: Mittel
- Voraussetzungen: Lektion 01-04 (Grundlagen), Python Kenntnisse
- Lernzeit: 45-60 Minuten
- Zielgruppe: Python-Entwickler, die schnelles Linting + Formatting suchen
🚀 Claude Code Relevanz: Ruff ist der ideale Begleiter fuer Claude Code Python-Workflows -- extrem schnelles Linting und Formatting in einem Tool, perfekt fuer die sofortige Validierung von AI-generiertem Python-Code.
Berechtigung: Warum ruff?
Problem ohne ruff:
# Alte Python-Toolchain: Langsam und fragmentiert
flake8 src/ # ~5s für Linting
pylint src/ # ~12s für erweiterte Checks
black src/ # ~3s für Formatting
isort src/ # ~2s für Import-Sorting
# Gesamt: ~22 Sekunden + 4 verschiedene Tools zu konfigurieren
Lösung mit ruff:
# Ruff: Alles in einem, 10-100x schneller
ruff check src/ # ~0.2s für Linting (alle flake8/pylint Rules!)
ruff format src/ # ~0.1s für Formatting (black-kompatibel)
# Gesamt: ~0.3 Sekunden + nur 1 Tool!
Benchmark-Vergleich:
# Repository: ~50k Zeilen Python-Code
# Alte Toolchain:
flake8: 5.2s
pylint: 14.8s
black: 3.1s
isort: 2.4s
Total: 25.5s
# Ruff:
ruff check: 0.18s (142x schneller als flake8+pylint!)
ruff format: 0.09s (34x schneller als black!)
Total: 0.27s (94x schneller gesamt!)
Kernvorteile:
✅ 10-100x schneller Rust-basiert, extrem performant
✅ All-in-One Ersetzt flake8, pylint, black, isort, pyupgrade
✅ 700+ Rules Alle wichtigen Linter in einem Tool
✅ Auto-Fixable >500 Rules mit automatischen Fixes
✅ Drop-in Replacement Kompatibel mit black + isort
✅ Zero-Config Funktioniert sofort ohne Setup
Zwecke: Wofür wird ruff verwendet?
. **Schnelles Linting (flake8 + pylint Ersatz)**
- Alle PEP 8 Violations finden
- Code-Smell erkennen (komplexe Funktionen, magic numbers)
- Best Practices durchsetzen (F-strings statt %, typing hints)
. **Code Formatting (black Ersatz)**
- Konsistente Code-Formatierung
- Line-Length-Management
- String-Quote-Normalisierung
. **Import-Management (isort Ersatz)**
- Imports automatisch sortieren
- Ungenutzte Imports entfernen
- Import-Gruppen organisieren (stdlib, third-party, local)
. **Code-Modernisierung (pyupgrade Ersatz)**
- Veraltete Syntax finden (z.B.
%statt f-strings) - Type-Hints modernisieren
- Alte Python-Patterns upgraden
. **CI/CD Integration**
- Pre-commit Hooks (extrem schnell)
- GitHub Actions (kompletter Check in <1s)
- Monorepo-Linting ohne Performance-Issues
Verwendung
Dieser Abschnitt zeigt dir Installation, Grundbefehle und erweiterte Konfiguration von ruff -- vom ersten Check bis zur vollstaendigen CI/CD-Integration.
Installation
Ruff kann auf verschiedenen Wegen installiert werden. Da es in Rust geschrieben ist und als einzige Binary ausgeliefert wird, ist die Installation sehr unkompliziert.
macOS (mit Homebrew):
# Via Homebrew (empfohlen für globale Installation)
brew install ruff
# Oder via pip (per Projekt)
pip install ruff --break-system-packages
# Oder via pipx (global, isoliert)
pipx install ruff
Ubuntu/Debian:
# Via pip
pip install ruff --break-system-packages
# Oder Binary Download
curl -LsSf https://astral.sh/ruff/install.sh | sh
# Oder via cargo (Rust)
cargo install ruff
Arch Linux:
# Via Pacman
sudo pacman -S ruff
# Oder via pip
pip install ruff --break-system-packages
Quick Start: Erste Schritte
Die wichtigsten ruff-Befehle sind ruff check (Linting) und ruff format (Formatierung). Beide arbeiten extrem schnell dank der Rust-Implementation.
. **Projekt linten**
Mit ruff check pruefst du Python-Code auf Fehler, Style-Verstoesse und Best-Practice-Verletzungen:
# Alle Python-Dateien linten
ruff check .
# Nur bestimmtes Verzeichnis
ruff check src/
# Mit Auto-Fix
ruff check --fix src/
# Bestimmte Datei
ruff check script.py
💡 Tipp: Nutzeruff check --fix .undruff format .als Kombination -- erst Linting mit Auto-Fix, dann Formatting. So erhaeltst du in unter einer Sekunde perfekt formatierten und gelinteten Code.
. **Code formatieren**
ruff format ist ein Drop-in-Replacement fuer black und formatiert Python-Code konsistent:
# Alle Dateien formatieren
ruff format .
# Nur src/ Verzeichnis
ruff format src/
# Check-Mode (ohne zu schreiben)
ruff format --check .
# Diff anzeigen
ruff format --diff script.py
. **Config erstellen (ruff.toml)**
Die ruff.toml-Datei steuert, welche Rules aktiv sind und wie die Formatierung aussehen soll:
cat > ruff.toml << 'EOF'
# Ruff Config
line-length = 100
[lint]
select = ["E", "F", "I"] # pycodestyle, Pyflakes, isort
ignore = ["E501"] # Line too long
[format]
quote-style = "double"
indent-style = "space"
EOF
. **Pre-commit Hook Setup**
# .pre-commit-config.yaml
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.15
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
EOF
# Pre-commit installieren
pre-commit install
. **VSCode Integration**
# Extension installieren
code --install-extension charliermarsh.ruff
# .vscode/settings.json
cat > .vscode/settings.json << 'EOF'
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.fixAll": true
}
}
}
EOF
Advanced Usage
Fortgeschrittene ruff-Konfiguration mit detaillierter Rule-Auswahl, Complexity-Checks und Format-Optionen.
. **Rule-Selection (alle wichtigen Linter)**
Ruff bringt ueber 700 Rules mit, organisiert nach Prefixen. Jeder Prefix steht fuer einen bestimmten Linter oder Check-Bereich:
# ruff.toml
[lint]
select = [
"E", # pycodestyle errors
"F", # Pyflakes
"W", # pycodestyle warnings
"I", # isort
"N", # pep8-naming
"D", # pydocstyle
"UP", # pyupgrade
"B", # flake8-bugbear
"C90", # mccabe (complexity)
"S", # flake8-bandit (security)
"T20", # flake8-print
"PT", # flake8-pytest-style
"RUF", # Ruff-specific rules
]
ignore = [
"D100", # Missing docstring in public module
"D104", # Missing docstring in public package
]
# Per-File Ignores
[lint.per-file-ignores]
"tests/*" = ["S101"] # Allow assert in tests
"__init__.py" = ["F401"] # Allow unused imports
⚠️ Warnung: Verwende--unsafe-fixesnur mit Vorsicht -- diese Fixes koennen das Verhalten des Codes aendern. Nutze im Zweifel--diffum die Aenderungen vorher zu pruefen.
. **Auto-Fix Everything**
# Alle fixbaren Issues automatisch fixen
ruff check --fix --unsafe-fixes src/
# Nur safe fixes (default)
ruff check --fix src/
# Diff preview vor Fix
ruff check --fix --diff src/
. **Complexity Checks (McCabe)**
# ruff.toml
[lint]
select = ["C90"]
[lint.mccabe]
max-complexity = 10 # Warnung bei Cyclomatic Complexity > 10
. **Type-Checking Integration (mypy-style)**
[lint]
select = ["ANN"] # flake8-annotations
[lint.flake8-annotations]
allow-star-arg-any = true
suppress-none-returning = true
. **Custom Ignore-Patterns**
# ruff.toml
extend-exclude = [
"migrations/",
"venv/",
"build/",
"dist/",
".venv/",
"*.egg-info/",
]
# File-specific ignores
[lint.per-file-ignores]
"settings.py" = ["E501"] # Lange Zeilen in Settings OK
"tests/**/*.py" = ["S101", "D"] # Assert + keine Docstrings in Tests
. **Format-Optionen**
# ruff.toml
line-length = 100
[format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "lf"
# Docstring-Formatting
docstring-code-format = true
docstring-code-line-length = 80
Best Practices
Bewaeaehrte Strategien fuer die Einfuehrung und den effektiven Einsatz von ruff in Projekten.
. **Stufenweises Rollout für Legacy-Projekte**
Bei einem bestehenden Projekt solltest du ruff schrittweise einfuehren, um nicht von hunderten Violations ueberwaeltigt zu werden:
# Phase 1: Nur Errors (nicht Warnings)
cat > ruff.toml << 'EOF'
[lint]
select = ["E", "F"] # Nur pycodestyle-errors + Pyflakes
EOF
ruff check --fix src/
# Phase 2: isort + pyupgrade
cat > ruff.toml << 'EOF'
[lint]
select = ["E", "F", "I", "UP"]
EOF
ruff check --fix src/
# Phase 3: Security + Bugbear
cat > ruff.toml << 'EOF'
[lint]
select = ["E", "F", "I", "UP", "S", "B"]
EOF
# Phase 4: Alles aktivieren
cat > ruff.toml << 'EOF'
[lint]
select = ["ALL"]
ignore = [
"D", # Docstrings später
]
EOF
🚀 Beispiel: Mit ruff check --statistics . erhaeltst du eine Uebersicht aller Violations nach Typ sortiert -- ideal, um die groessten Problembereiche in einem Legacy-Projekt schnell zu identifizieren.
. **CI/CD Integration (GitHub Actions)**
# .github/workflows/lint.yml
name: Ruff
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Ruff
run: pip install ruff
- name: Run Ruff Linter
run: ruff check --output-format=github .
- name: Run Ruff Formatter
run: ruff format --check .
. **Monorepo mit mehreren Configs**
# root/ruff.toml (global config)
line-length = 100
[lint]
select = ["E", "F"]
# services/api/ruff.toml (override)
extend = "../../ruff.toml"
[lint]
select = ["E", "F", "S"] # + Security für API
# scripts/ruff.toml (override)
extend = "../ruff.toml"
[lint]
ignore = ["T20"] # Prints erlaubt in Scripts
. **IDE-Integration (alle gängigen Editoren)**
# VSCode: charliermarsh.ruff
# PyCharm: Ruff Plugin
# Vim: ALE + ruff
# Emacs: flycheck-ruff
# VSCode settings.json
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": true,
"source.organizeImports.ruff": true
}
},
"ruff.lint.args": ["--config=ruff.toml"]
}
💡 Tipp: Ruff ersetzt flake8, pylint, black, isort und pyupgrade in einem einzigen Tool. Bei der Migration kannst du deine bestehende flake8/black-Konfiguration fast 1:1 in ruff.toml uebertragen.
. **Watch-Mode für Development**
# Mit entr (aus Lektion 19)
fd -e py | entr -c ruff check --fix /_
# Oder mit nodemon
nodemon --watch src/ --ext py --exec 'ruff check --fix src/ && ruff format src/'
Beispiele
Beispiel 1: Unused Imports finden + fixen
# Vorher: messy.py
import os
import sys # ❌ Unused
from typing import List, Dict, Optional # ❌ Dict unused
import json
def get_env():
return os.getenv("API_KEY")
# Ruff
$ ruff check messy.py
messy.py:2:8: F401 [*] sys imported but unused
messy.py:3:24: F401 [*] typing.Dict imported but unused
# Auto-Fix
$ ruff check --fix messy.py
# Nachher:
import os
from typing import List, Optional
import json
def get_env():
return os.getenv("API_KEY")
Beispiel 2: F-String Modernisierung
# Vorher: old-style.py
name = "Alice"
age = 30
# ❌ Old-style string formatting
msg1 = "Hello, %s!" % name
msg2 = "You are {} years old".format(age)
msg3 = "Name: {}, Age: {}".format(name, age)
# Ruff (mit UP rule)
$ ruff check old-style.py
old-style.py:5:8: UP031 [*] Use format specifiers instead of percent format
old-style.py:6:8: UP032 [*] Use f-string instead of format call
# Auto-Fix
$ ruff check --fix old-style.py
# Nachher:
name = "Alice"
age = 30
msg1 = f"Hello, {name}!"
msg2 = f"You are {age} years old"
msg3 = f"Name: {name}, Age: {age}"
Beispiel 3: Import-Sorting (isort-kompatibel)
# Vorher: imports.py (durcheinander)
from app.models import User
import sys
from typing import List
import os
from app.utils import helper
# Ruff
$ ruff check --select I imports.py
imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
# Auto-Fix
$ ruff check --fix --select I imports.py
# Nachher: (stdlib → third-party → local)
import os
import sys
from typing import List
from app.models import User
from app.utils import helper
Beispiel 4: Security-Issues finden
# Vorher: unsafe.py
import pickle # ❌ Unsicher
import subprocess
def load_data(filename):
with open(filename, 'rb') as f:
return pickle.load(f) # ❌ pickle.load ist unsicher
def run_command(cmd):
subprocess.call(cmd, shell=True) # ❌ shell=True ist gefährlich
# Ruff (mit Security rules)
$ ruff check --select S unsafe.py
unsafe.py:1:8: S403 pickle is not secure; prefer json
unsafe.py:6:12: S301 pickle.load is unsafe
unsafe.py:9:5: S602 shell=True is dangerous
# Empfohlene Fixes (manuell):
import json
def load_data(filename):
with open(filename, 'r') as f:
return json.load(f) # ✓ Sicher
def run_command(cmd):
subprocess.run(cmd, shell=False, check=True) # ✓ Sicher
Beispiel 5: Code-Complexity Warnung
# Vorher: complex.py
def process_order(order):
if order['type'] == 'premium':
if order['amount'] > 100:
if order['customer']['vip']:
if order['shipping'] == 'express':
# ... deep nesting
pass
# Ruff (McCabe Complexity)
$ ruff check --select C90 complex.py
complex.py:1:1: C901 process_order is too complex (8 > 5)
# Refactoring-Empfehlung:
def process_order(order):
is_premium = order['type'] == 'premium'
is_high_value = order['amount'] > 100
is_vip = order['customer']['vip']
is_express = order['shipping'] == 'express'
if is_premium and is_high_value and is_vip and is_express:
# ... (Complexity: 2)
pass
Beispiel 6: Formatting (black-kompatibel)
# Vorher: unformatted.py
def foo(x,y,z):return x+y+z
result=foo(1,2,3)
my_list=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
# Ruff Format
$ ruff format unformatted.py
# Nachher:
def foo(x, y, z):
return x + y + z
result = foo(1, 2, 3)
my_list = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
]
Beispiel 7: Type-Annotations prüfen
# Vorher: no-types.py
def calculate(x, y): # ❌ Keine Type-Hints
return x + y
# Ruff (flake8-annotations)
$ ruff check --select ANN no-types.py
no-types.py:1:1: ANN201 Missing return type annotation for public function
no-types.py:1:15: ANN001 Missing type annotation for function argument x
no-types.py:1:18: ANN001 Missing type annotation for function argument y
# Fixed:
def calculate(x: int, y: int) -> int:
return x + y
Beispiel 8: Print-Statement-Warnung (Production-Code)
# Vorher: debug.py
def process_data(data):
print("Processing:", data) # ❌ Print in Production
result = transform(data)
print("Result:", result) # ❌ Print in Production
return result
# Ruff (flake8-print)
$ ruff check --select T20 debug.py
debug.py:2:5: T201 print found
# Empfehlung: Logging verwenden
import logging
logger = logging.getLogger(__name__)
def process_data(data):
logger.info("Processing: %s", data) # ✓ Proper logging
result = transform(data)
logger.debug("Result: %s", result)
return result
Beispiel 9: Pytest-Style Best Practices
# Vorher: test_api.py
import pytest
def test_user_creation():
user = create_user()
assert user.name == "Alice" # OK
assert user.email == "alice@example.com" # OK
assert user != None # ❌ Should use 'is not None'
# Ruff (pytest-style)
$ ruff check --select PT test_api.py
test_api.py:6:12: PT018 Assertion should be assert x is not None
# Auto-Fix:
assert user is not None # ✓ Correct
Beispiel 10: Batch-Processing großer Projekte
# Alle Dateien linten und formatieren
ruff check --fix . && ruff format .
# Mit Statistiken
ruff check . --statistics
# Output:
# 143 F401 [*] imported but unused
# 82 E501 Line too long
# 34 UP031 [*] Use f-string
# 18 S101 Use of assert detected
# Nur fixbare Issues fixen
ruff check --fix --select F,UP .
# Report in JSON für Weiterverarbeitung
ruff check --output-format=json . > ruff-report.json
Integration in Claude Code Workflows
. **AI-gestützte Ruff-Config-Optimierung**
# Ruff-Report generieren
ruff check --output-format=json src/ > ruff-report.json
# Von Claude analysieren lassen
cat ruff-report.json | claude "Analyze these Ruff violations and suggest config improvements"
# Custom Rule-Sets vorschlagen lassen
cat ruff.toml | claude "Optimize this Ruff config for a FastAPI project"
. **Automated Refactoring**
# Workflow: Ruff findet Issues → Claude schlägt Refactorings vor
cat > auto-refactor.sh << 'EOF'
#!/bin/bash
# Ruff-Violations sammeln
ruff check --output-format=json src/ > violations.json
# Claude analysiert und schlägt Fixes vor
cat violations.json | claude "Generate refactoring suggestions for these code issues" > suggestions.md
echo "Review suggestions.md for refactoring ideas"
EOF
chmod +x auto-refactor.sh
. **Pre-Review Ruff-Check**
# Vor Code-Review: Alle Issues finden
git diff main...HEAD --name-only | \
grep '\.py