8. Professionelle Entwicklung mit Python¶
Professionelle Python-Entwicklung besteht nicht nur aus Sprachsyntax. Entscheidend ist, wie ein Projekt eingerichtet, strukturiert, getestet, formatiert und überprüft wird.
In modernen Python-Projekten übernimmt uv viele Aufgaben, die früher auf mehrere Werkzeuge verteilt waren. Projekte werden initialisiert, Abhängigkeiten verwaltet, Entwicklungswerkzeuge installiert und reproduzierbare Umgebungen synchronisiert. Zusammen mit pytest, Ruff und Mypy entsteht daraus ein kompakter, leistungsfähiger Workflow.
Dieses Kapitel zeigt einen pragmatischen Entwicklungsprozess ohne unnötigen Werkzeugmix. Der Fokus liegt auf uv als Projektwerkzeug, Ruff für Linting und Formatierung, pytest für Tests und Mypy für statische Typprüfung.
Ziel ist es, ein professionelles Python-Projekt aufzubauen, das sauber strukturiert, automatisch prüfbar und langfristig wartbar ist.
Der professionelle Python-Workflow¶
Moderne Entwicklungswerkzeuge im Python-Workflow¶
Professionelle Python-Entwicklung basiert heute auf einem klar definierten Workflow, der weit über das reine Schreiben von Code hinausgeht. Moderne Werkzeuge übernehmen Aufgaben wie Abhängigkeitsmanagement, Testausführung, statische Analyse und Formatierung. Diese Automatisierung sorgt für Konsistenz, Wiederholbarkeit und Qualität – Aspekte, die auch erfahrene Entwickler aus Java-, C#- oder C++-Umgebungen kennen, aber in Python oft anders umgesetzt werden.
Einheitlicher Workflow: Warum er wichtig ist¶
Ein einheitlicher Workflow stellt sicher, dass alle Teammitglieder unter identischen Bedingungen entwickeln und testen. Anders als in klassischen Build-Systemen, die oft komplexe Konfigurationsdateien und explizite Kompilationsschritte erfordern, setzt Python auf schlanke, deklarative Konfigurationen (z. B. pyproject.toml) und isolierte Umgebungen. Das minimiert „funktioniert-nur-bei-mir“-Probleme und fördert die Wartbarkeit.
Aufgaben moderner Werkzeuge im Überblick¶
- Projektinitialisierung: Automatisches Erzeugen einer konsistenten Projektstruktur, die Best Practices wie das
src-Layout unterstützt. - Abhängigkeitsmanagement: Verwaltung von Laufzeit- und Entwicklungsabhängigkeiten mit präziser Versionskontrolle und Sperrdateien.
- Testausführung: Integration von Testframeworks wie pytest, um Tests einfach und reproduzierbar auszuführen.
- Codequalität: Automatisiertes Linting und Formatierung (z. B. mit Ruff), um Stil- und Fehlerquellen frühzeitig zu erkennen.
- Statische Typprüfung: Einsatz von Mypy für frühzeitige Erkennung von Typfehlern, was besonders bei dynamischer Typisierung wertvoll ist.
Beispiel: Workflow mit uv¶
Das Tool uv bündelt viele dieser Aufgaben und ermöglicht einen konsistenten Ablauf:
uv init my_project # Projektstruktur erzeugen
uv add requests # Laufzeitabhängigkeit hinzufügen
uv add --dev pytest mypy ruff # Entwicklungsabhängigkeiten hinzufügen
uv run pytest # Tests ausführen
uvx ruff # Linting und Formatierung
uv run mypy # Statische Typprüfung
Dieser Workflow stellt sicher, dass alle Schritte innerhalb derselben isolierten Umgebung ablaufen, was Versionskonflikte vermeidet und reproduzierbare Ergebnisse liefert.
Best Practice: Trennung von Laufzeit- und Entwicklungsabhängigkeiten¶
In Python-Projekten ist es üblich, zwischen Abhängigkeiten für den Produktivcode und Tools für Entwicklung und Testing zu unterscheiden. Das verhindert, dass z. B. Testframeworks in der Produktionsumgebung installiert werden müssen. Werkzeuge wie uv unterstützen diese Trennung nativ.
Ein Projekt mit uv init erstellen¶
Ein neues Python-Projekt mit uv init starten¶
Mit uv init erzeugen Sie eine moderne Python-Projektstruktur, die bewährte Konventionen und Best Practices berücksichtigt und zwei etablierte Layouts unterstützt: das Flat-Layout und das src-Layout.
Flat-Layout vs. src-Layout¶
- Flat-Layout:
- Der Quellcode liegt direkt im Projektstammverzeichnis in einem Paketordner, z.B.
my_project/. - Vorteile: Einfachheit, weniger Verzeichnistiefe.
- Nachteile: Gefahr von Namenskonflikten mit anderen Dateien im Projekt, weniger klare Trennung von Quellcode und anderen Ressourcen.
my_project/
├──my_project/
│ ├── main.py
│ ├── api.py
│ ├── models.py
│ ├── services.py
│ └── utils.py
├── tests/
├── pyproject.toml
├── README.md
└── .gitignore
- src-Layout:
- Der Quellcode liegt unter
src/(z.B.src/my_project/). - Vorteile: Klare Trennung von Quellcode und anderen Dateien (Tests, Konfiguration, Dokumentation).
- Verhindert versehentliches Importieren von Dateien außerhalb des Quellcodes.
- Wird in größeren Projekten oder Bibliotheken empfohlen.
my_project/
├── src/
│ └── my_project/
│ ├── main.py
│ ├── api.py
│ ├── models.py
│ ├── services.py
│ └── utils.py
├── tests/
├── pyproject.toml
├── README.md
└── .gitignore
uv init fragt bei der Projekterstellung nach dem bevorzugten Layout und generiert die entsprechende Struktur.
Beispiel: Projektinitialisierung mit uv init my_project --package¶
Nach Abschluss finden Sie eine Struktur ähnlich dieser vor:
my_project/
├── pyproject.toml
├── README.md
├── src/ # Nur bei src-Layout (mit der --package oder --lib Option bei uv)
│ └── my_project/
│ └── __init__.py
Warum diese Trennung?¶
Das src-Layout verhindert, dass Tests oder andere Dateien versehentlich als Module importiert werden, was in großen Projekten häufig zu subtilen Fehlern führt.
Best Practice: src-Layout bevorzugen¶
Für professionelle Projekte, vor allem später via pip installiebare, empfiehlt sich das src-Layout, da es:
- die Trennung von Quellcode und anderen Ressourcen sicherstellt
- die Testumgebung sauber hält
- den Importmechanismus klar definiert
Beispiel für eine einfache Paketstruktur im einfachen Flat-Layout¶
Hier liegt alles im Projektstamm, was für kleine Projekte oder Skripte ausreichend sein kann. Allerdings können bei größeren Projekten schnell Importkonflikte auftreten, wenn z.B. eine Datei utils.py existiert und gleichzeitig ein Paket utils importiert werden soll.
In api.py könnten Sie z.B. eine REST-API-Client-Klasse definieren:
class ApiClient:
def __init__(self, base_url: str) -> None:
self.base_url = base_url
def fetch_data(self, endpoint: str) -> dict:
# Beispielhafte Methode, die Daten von einer API holt
pass
Diese Struktur lässt sich problemlos mit uv run und anderen Werkzeugen nutzen, die auf der Projektstruktur aufbauen.
Zusammenfassung¶
uv initerzeugt ein neues Python-Projekt mit moderner Struktur.- Zwei Layouts stehen zur Wahl: Flat (einfach) und src (empfohlen für professionelle Projekte).
- Das
src-Layout trennt Quellcode klar von anderen Dateien und verhindert Importprobleme. - Die Wahl der Projektstruktur ist ein wichtiger Schritt, der spätere Wartbarkeit und Tool-Integration erleichtert.
Abhängigkeiten mit uv add verwalten¶
Grundprinzipien der Abhängigkeitsverwaltung mit uv¶
In Python-Projekten ist die Verwaltung von Abhängigkeiten zentral für Reproduzierbarkeit, Wartbarkeit und Teamarbeit. Das Werkzeug uv integriert Paketmanagement nahtlos in den Workflow und nutzt dabei das standardisierte pyproject.toml als zentrale Konfigurationsdatei.
uv add ist der Befehl, um Abhängigkeiten hinzuzufügen oder zu entfernen. Dabei unterscheidet man zwischen Laufzeitabhängigkeiten (dependencies) und Entwicklungsabhängigkeiten (dev-dependencies).
Laufzeitabhängigkeiten hinzufügen¶
Laufzeitabhängigkeiten sind Bibliotheken, die für den Betrieb der Anwendung notwendig sind. Beispiel: Ein Web-API-Projekt benötigt fastapi und httpx.
Dies aktualisiert automatisch die Sektion [dependencies] in pyproject.toml und sorgt dafür, dass die Abhängigkeiten in der virtuellen Umgebung installiert werden.
Entwicklungsabhängigkeiten verwalten¶
Entwicklungsabhängigkeiten sind Tools, die nur während Entwicklung und Test benötigt werden, z.B. pytest oder mypy.
Diese landen in der Sektion [dependency-groups] und werden nicht mit in die Produktionsumgebung übernommen. Das entspricht in Java etwa dem test-Scope in Maven.
Entfernen von Abhängigkeiten¶
Um eine Abhängigkeit zu entfernen, nutzt man:
Dies entfernt die Bibliothek sowohl aus der pyproject.toml als auch aus der virtuellen Umgebung.
Synchronisation der Abhängigkeiten¶
uv nutzt eine Lock-Datei (uv.lock), um die exakten Versionen der installierten Pakete festzuhalten. Das sorgt für deterministische Builds und verhindert "funktioniert-nur-bei-mir"-Probleme.
Nach Änderungen an pyproject.toml oder wenn man ein Projekt aus einem Versionskontrollsystem auscheckt, synchronisiert man die Umgebung mit:
Dieser Befehl installiert alle Abhängigkeiten exakt in den Versionen, die in uv.lock definiert sind.
Best Practices¶
- Trennung von Laufzeit- und Entwicklungsabhängigkeiten: Vermeidet unnötige Pakete in der Produktion und reduziert Sicherheitsrisiken.
- Lock-Datei nutzen: Immer
uv.lockversionieren, um reproduzierbare Builds sicherzustellen. - Explizite Versionen bevorzugen: Nutze Versionseinschränkungen (z.B.
fastapi>=0.95,<1.0), um unerwartete Updates zu vermeiden.
Beispiel: Schritt-für-Schritt¶
- Neues Projekt initialisieren:
- Laufzeitabhängigkeiten hinzufügen:
- Entwicklungsabhängigkeiten hinzufügen:
- Synchronisieren (z.B. nach Auschecken aus Git):
- Entfernen einer Abhängigkeit:
Fazit¶
uv add und die zugehörigen Kommandos bieten eine klare, deklarative Schnittstelle für die Abhängigkeitsverwaltung in Python. Sie fördern eine saubere Trennung von Laufzeit- und Entwicklungsabhängigkeiten, ermöglichen reproduzierbare Builds durch Lock-Dateien und integrieren sich nahtlos in moderne Python-Workflows.
Programme mit uv run starten¶
uv run: Programme und Tools konsistent ausführen¶
Der Befehl uv run ist das zentrale Werkzeug, um Python-Code innerhalb der isolierten Projektumgebung auszuführen. Im Gegensatz zu globalen Python-Installationen sorgt uv run dafür, dass alle Abhängigkeiten, Umgebungsvariablen und Konfigurationen exakt wie im Projekt definiert geladen werden. Dies verhindert Versionskonflikte und unvorhersehbares Verhalten, das in anderen Sprachen oft durch unterschiedliche globale Umgebungen entsteht.
Warum nicht einfach python oder pytest direkt aufrufen?¶
Python-Projekte profitieren stark von virtuellen Umgebungen, da die Sprache selbst keine zentrale Paketverwaltung erzwingt. uv run kapselt den Aufruf von Python-Interpreter, Test-Frameworks oder anderen Tools so, dass immer die projektdefinierte Umgebung genutzt wird. Dadurch entfällt das manuelle Aktivieren von virtuellen Umgebungen oder das Risiko, globale Pakete zu verwenden.
Grundlegende Nutzung¶
Um ein Skript auszuführen, verwenden Sie:
Hierbei startet uv run den Python-Interpreter mit der im Projekt definierten Umgebung.
Sie können ein bestimmtes Modul direkt ausführen:
So kann die Projektstruktur auch mitsrc-Layout problemlos genutzt werden, da uv run den PYTHONPATH entsprechend setzt.
Tests ausführen¶
Tests werden mit pytest typischerweise so gestartet:
Dies garantiert, dass die Version von pytest verwendet wird, die im Projekt als Entwicklungsabhängigkeit definiert ist.
Werkzeuge und CLI-Tools¶
Auch Code-Formatierer, Linter oder statische Analysewerkzeuge werden über uv run ausgeführt, z.B.:
Dadurch bleibt die Toolchain reproduzierbar und unabhängig von globalen Installationen.
Best Practice: Konsistente Projektumgebung¶
- Verwenden Sie immer
uv run, um sicherzustellen, dass der korrekte Interpreter und die korrekten Abhängigkeiten geladen werden. - Vermeiden Sie globale Python- oder Tool-Installationen, um Versionskonflikte zu minimieren.
- Automatisieren Sie häufige Befehle in Makefiles oder Shell-Skripten, die
uv runnutzen.
Tests mit pytest schreiben¶
Grundprinzipien von pytest¶
Pytest ist das bevorzugte Testframework in der Python-Welt, da es durch seine einfache Syntax und mächtige Features überzeugt. Anders als in Java oder C#, wo Testmethoden oft durch spezielle Annotationen markiert werden, erkennt pytest automatisch alle Funktionen und Methoden, die mit test_ beginnen. Dies fördert eine minimalistische, aber ausdrucksstarke Testdefinition.
Ein typischer pytest-Test ist eine einfache Funktion, die Assertions verwendet, um Verhalten zu prüfen:
Die Verwendung von assert ist idiomatisch und unterscheidet sich von JUnit oder NUnit, wo spezielle Assert-Methoden genutzt werden. Pytest wertet assert-Ausdrücke aus und gibt bei Fehlern detaillierte Informationen, was das Debuggen erleichtert.
Strukturierung von Tests¶
Tests sollten in einem eigenen Verzeichnis, z.B. tests/, liegen. Innerhalb dieses Verzeichnisses können Module parallel zur Produktivcode-Struktur angelegt werden. So bleibt die Teststruktur übersichtlich und nachvollziehbar.
your_project/
├── src/
│ └── your_project/
│ ├── __init__.py
│ ├── calculator.py
│ └── cli.py
├── tests/
│ ├── test_calculator.py
│ └── test_cli.py
├── pyproject.toml
├── README.md
Diese Trennung unterstützt den Workflow mit uv run pytest, da so die Testumgebung klar definiert ist und Importprobleme vermieden werden.
Testausführung mit uv¶
Mit uv run pytest wird pytest innerhalb der isolierten Projektumgebung ausgeführt. Das vermeidet globale Abhängigkeiten und sorgt für reproduzierbare Testergebnisse.
Standardmäßig sucht pytest rekursiv nach Tests im Projektverzeichnis. Für gezielte Ausführung können Pfade oder Testnamen angegeben werden:
Mehr zum Thema pytest und Testorganisation findet sich in Kapitel 13 Automatisierte Tests mit pytest.
Linting und Formatierung mit uvx ruff¶
Ruff ohne globale Installation nutzen¶
Ruff ist ein moderner Linter und Formatter, der in Rust implementiert ist und eine beeindruckende Performance bietet. Anders als in vielen Java- oder C#-Workflows, wo Linter oft global installiert und konfiguriert werden, empfiehlt sich in Python die lokale Nutzung innerhalb der Projektumgebung. Das vermeidet Versionskonflikte und sorgt für reproduzierbare Builds.
Mit uvx, dem Kommandozeilenwerkzeug aus dem uv-Ökosystem, lässt sich Ruff bequem ohne globale Installation ausführen:
Hierbei sorgt uvx dafür, dass die richtige Ruff-Version aus den Abhängigkeiten geladen wird. Das entspricht dem Prinzip von "lokalen Tools" in Node.js oder .NET, das in Python zunehmend Standard wird.
Um tools wie mypy oder ruff direkt auszuführen, ohne uv run zu verwenden, bietet uvx eine praktische Abkürzung.
Ein vorheriges Installieren in einer dev-dependencies-Gruppe ist dabei NICHT notwendig.
Konfiguration zentral in pyproject.toml¶
Die Konfiguration von Ruff erfolgt zentral in der pyproject.toml. Das ist ein großer Vorteil gegenüber klassischen Java- oder C#-Projekten, die oft auf mehrere Konfigurationsdateien verteilt sind. Ein Beispiel:
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "C90"]
ignore = ["E203", "W503"]
exclude = ["tests/data"]
line-lengthdefiniert die maximale Zeilenlänge, ähnlich wie in vielen Formatter-Tools.selectbestimmt die zu aktivierenden Regelkategorien.ignoreblendet spezifische Regeln aus, z.B. um Konflikte mit Black zu vermeiden.excludeschließt bestimmte Verzeichnisse aus der Prüfung aus.
Diese zentrale Steuerung fördert Konsistenz und erleichtert die Zusammenarbeit im Team.
Automatisierte Formatierung und Linting im Workflow¶
Ruff kann nicht nur Fehler anzeigen, sondern auch automatisch formatieren:
Integration in den Entwicklungsprozess¶
In professionellen Python-Projekten empfiehlt es sich, Ruff als festen Bestandteil des CI/CD-Prozesses zu integrieren. Ein typischer Workflow:
- Entwickler führen
uvx ruff checklokal vor dem Commit aus oder in einempre-commit-Hook. - CI-Server prüft den Code erneut und blockiert Builds bei Linting-Fehlern.
- Optional: Automatische Formatierung vor dem Merge.
Dadurch wird verhindert, dass stilistische oder einfache Fehler in den Hauptzweig gelangen.
Best Practices¶
- Nutze
pyproject.tomlals zentrale Konfigurationsquelle für Ruff und andere Tools. - Führe Linting und Formatierung regelmäßig lokal und im CI aus.
- Vermeide globale Tool-Installationen, um Versionskonflikte zu verhindern.
- Verwende
uvx rufffür konsistente Ausführung und einfache Integration.
Statische Typprüfung mit Mypy¶
Warum statische Typprüfung in Python?¶
Python ist eine dynamisch typisierte Sprache, was Flexibilität und schnelle Entwicklung ermöglicht. Gerade für erfahrene Entwickler aus statisch typisierten Sprachen wie Java oder C# stellt sich jedoch oft die Frage: Wie kann man in Python Typfehler frühzeitig erkennen, ohne die Dynamik zu verlieren?
Hier kommt Mypy ins Spiel. Mypy ist ein statischer Typprüfer, der auf den optionalen Type Hints in Python basiert. Er überprüft den Code ohne Ausführung und hilft, Fehler frühzeitig zu finden, die sonst erst zur Laufzeit auftreten würden.
Type Hints idiomatisch einsetzen¶
Python bietet Type Hints seit PEP 484 an. Anders als in Java oder C# sind sie optional und beeinflussen die Laufzeit nicht. Das bedeutet, dass sie primär der Lesbarkeit, Dokumentation und Werkzeugunterstützung dienen.
Beispiel einer Funktion mit Type Hints:
from typing import Optional
def fetch_user(user_id: int) -> Optional[dict]:
# Gibt ein Benutzer-Dictionary zurück oder None, wenn nicht gefunden
...
Wichtig ist, dass Type Hints in Python eher als Vertrag und Dokumentation verstanden werden, die von Tools wie Mypy überprüft werden.
Mypy konfigurieren und ausführen¶
Mypy liest die Typinformationen aus dem Code und prüft sie gegen die tatsächliche Verwendung. Für ein modernes Python-Projekt empfiehlt sich die zentrale Konfiguration in pyproject.toml oder früher auch in einer mypy.ini:
[tool.mypy]
python_version = "3.13"
disallow_untyped_defs = true # Keine untypisierten Funktionen
ignore_missing_imports = true # Für externe Libraries
strict_optional = true # Optional-Typen streng prüfen
Ein typischer Mypy-Check wird über den Befehl uv run mypy ausgeführt, wenn Mypy als Entwicklungsabhängigkeit installiert ist.
oder kürzer mit uvx:
Typfehler frühzeitig erkennen¶
Mypy erkennt typische Fehler, z.B. falsche Rückgabetypen, fehlende Argumente oder falsche Typen in Collections:
def process_items(items: list[int]) -> int:
return sum(items)
result = process_items([1, 2, '3']) # Fehler: '3' ist kein int
Dies verhindert Laufzeitfehler, die in dynamischen Tests oft schwer zu reproduzieren sind.
Schrittweise strengere Typprüfung¶
Gerade bei großen Codebasen empfiehlt sich ein schrittweises Vorgehen:
disallow_untyped_defs = falsestarten, um Typprüfung nur bei vorhandenen Hints zu aktivieren.- Nach und nach Funktionen typisieren und
disallow_untyped_defsauftruesetzen. strict_optionalund weitere strenge Optionen aktivieren, um Null-Sicherheiten zu erhöhen.
So lässt sich der Aufwand kontrolliert verteilen.
Best Practices¶
- Verwenden Sie Type Hints konsequent, vor allem in öffentlichen APIs.
- Nutzen Sie Mypy oder Pyright in der IDE für sofortiges Feedback.
- Nutzen Sie Mypy als Teil des CI-Prozesses.
- Arbeiten Sie mit pre-commit Hooks, um Typprüfungen vor dem Commit sicherzustellen.
- Pflegen Sie eine zentrale Konfiguration, um Konsistenz sicherzustellen.
- Kombinieren Sie Typprüfung mit Unit Tests für maximale Sicherheit.
Clean Code in Python¶
Pythonische Lesbarkeit und Einfachheit¶
In Python steht Lesbarkeit an erster Stelle. Anders als in Java oder C# ist explizite Typisierung nicht zwingend.
class User:
def __init__(self, name: str):
self.name = name
@property
def display_name(self) -> str:
return self.name.title()
Hier ersetzt @property die klassische Getter-Methode und hält den Zugriff einfach und intuitiv.
Vermeidung unnötiger Abstraktionen¶
Java- oder C++-Entwickler neigen dazu, viele Interfaces oder abstrakte Klassen zu definieren. In Python ist das oft überflüssig, da Duck Typing und dynamische Typisierung Flexibilität schaffen. Statt komplexer Vererbungsbäume sind einfache Funktionen oder Klassen mit klar definierten Schnittstellen idiomatisch.
Das ist oft besser als eine abstrakte Basisklasse mit vielen Methoden.
Klare und aussagekräftige Namen¶
Python legt großen Wert auf aussagekräftige, aber nicht übermäßig lange Namen. Anders als in manchen Java-Konventionen, wo oft Präfixe oder Suffixe verwendet werden, bevorzugt Python Klarheit durch Kontext und einfache Namen.
# Gut und Snake Case
def calculate_tax(amount: float) -> float:
return amount * 0.19
# Weniger idiomatisch (und kein Snake Case)
def calcTaxAmount(amountValue: float) -> float:
return amountValue * 0.19
Funktionen als First-Class Citizens nutzen¶
Python fördert funktionale Programmierungselemente. Funktionen können einfach als Argumente übergeben oder zurückgegeben werden, was in Java oder C# oft umständlicher ist.
from typing import Callable
def apply_operation(data: list[int], op: Callable) -> list[int]:
return [op(x) for x in data]
result = apply_operation([1, 2, 3], lambda x: x + 1)
Vermeidung von unnötiger Sichtbarkeitskontrolle¶
In Java oder C# sind private und protected essenziell. Python folgt hier einer anderen Philosophie: "We are all consenting adults". Ein einfacher Unterstrich _ signalisiert eine interne API, aber es gibt keine echten Zugriffsmodifikatoren.
class Service:
def _helper(self) -> None:
"""interne Methode, aber nicht wirklich privat."""
pass
def transform(self, data) -> list[int]:
"""öffentliche Methode, die die interne Methode nutzt."""
self._helper() # Aufruf der internen Methode
return [x * 2 for x in data]
service = Service()
service._helper() # Technisch möglich, aber signalisiert "nicht für den externen Gebrauch"
service.transform([1, 2, 3]) # Korrekte Nutzung der öffentlichen Methode
Idiomatische Fehlerbehandlung¶
Python verwendet Exceptions, aber der Umgang ist oft lockerer als in stark typisierten Sprachen. Es ist üblich, Fehler dort zu behandeln, wo sie sinnvoll sind, und nicht übermäßig viele Checked Exceptions zu deklarieren.
Fazit¶
Clean Code in Python bedeutet, sich auf die Sprache und ihre Philosophie einzulassen. Statt gewohnte Muster aus Java oder C# 1:1 zu übertragen, sollte man die Stärken von Python nutzen: klare, prägnante Syntax, dynamische Typisierung, flexible Funktionen und eine pragmatische Fehlerbehandlung.
So entsteht wartbarer, lesbarer und idiomatischer Code, der sich von klassischen objektorientierten Paradigmen löst, ohne an Professionalität einzubüßen.
Praxisprojekt: Ein kleine API¶
Projektinitialisierung und Struktur¶
Wir starten mit einem neuen Projekt, das wir mit uv init anlegen. Dabei wählen wir das src-Layout, da es eine klare Trennung zwischen Quellcode und Tests/Dokumentation ermöglicht und Importprobleme vermeidet – ein häufig unterschätztes Problem, das in Java/C# meist durch IDEs und Build-Tools abstrahiert wird.
Die generierte Struktur sieht typischerweise so aus:
Das src-Verzeichnis schützt vor versehentlichen Importen aus dem Arbeitsverzeichnis und erzwingt saubere Paketgrenzen.
Abhängigkeiten verwalten¶
Wir fügen Laufzeit- und Entwicklungsabhängigkeiten mit uv add hinzu. Entwicklungsabhängigkeiten wie pytest, ruff und mypy kommen in den [tool.uv.dev-dependencies]-Block in der pyproject.toml, was klar signalisiert, dass sie nicht im Produktivcode benötigt werden.
Das Synchronisieren der Abhängigkeiten mit uv sync stellt sicher, dass venv und Lock-Datei konsistent sind.
Code ausführen und testen¶
Mit uv run führen wir Code und Tests innerhalb der isolierten Umgebung aus:
Linting und Formatierung¶
Statische Typprüfung¶
DAs API-Modul¶
# src/app/api.py
from typing import TypedDict
import requests
class User(TypedDict):
id: int
name: str
email: str
def fetch_user(user_id: int) -> User | None:
response = requests.get(
f"https://jsonplaceholder.typicode.com/users/{user_id}"
)
if response.status_code != 200:
return None
data = response.json()
return User(
id=data["id"],
name=data["name"],
email=data["email"],
)
# tests/test_api.py
from app.api import fetch_user
def test_fetch_user_returns_user() -> None:
user = fetch_user(1)
assert user is not None
assert user["id"] == 1
assert isinstance(user["email"], str)
Auf Mocks oder Stubs verzichten wir hier bewusst, um den Fokus auf die Integration von Werkzeugen und den Workflow zu legen. In einem echten Projekt würden wir natürlich externe API-Aufrufe mocken, um Tests stabil und unabhängig von externen Diensten zu machen.
Zusammenführung der Werkzeuge¶
uv initlegt das Projekt mit sauberer Struktur an.uv addverwaltet Abhängigkeiten präzise.uv rununduvxführen Code, Tests und Werkzeuge isoliert aus.pytestsichert funktionale Korrektheit.ruffsorgt für konsistenten Stil und vermeidet Fehler.mypyerhöht die Codequalität durch Typprüfung.
Diese Kombination entspricht einem modernen, professionellen Workflow, der in Python die Balance zwischen Flexibilität und Sicherheit wahrt.