Virtual environment, pip und pyproject.toml in Python
1. Übersicht
Wenn du an mehreren Python-Projekten auf derselben Maschine arbeitest, wirst du bald auf Versionskonflikte stoßen. Projekt A benötigt requests 2.28, Projekt B dagegen requests 2.32. Dieser Artikel führt drei Werkzeuge ein, die das Problem lösen. venv isoliert die Umgebung für jedes Projekt. pip installiert und verwaltet Pakete. pyproject.toml deklariert das Projekt nach modernem Standard. Nach dem Lesen wirst du in der Lage sein, ein strukturiertes Python-Projekt zu erstellen, es im editierbaren Modus zu installieren und Tests mit pytest auszuführen.
2. Virtuelle Umgebung
Eine virtuelle Umgebung (venv) ist ein Verzeichnis, das drei Dinge enthält: einen separaten Python-Interpreter (normalerweise ein Symlink), ein eigenes Verzeichnis site-packages und Aktivierungsskripte. Pakete, die du in der venv installierst, befinden sich nur in diesem Verzeichnis und beeinflussen nicht das Python der Systeminstallation.
2.1. venv erstellen und aktivieren
Das Modul venv ist seit Python 3.3 in der Standardbibliothek enthalten, du brauchst also nichts zusätzlich zu installieren:
mkdir shopcart && cd shopcart
python3 -m venv .venv
# Linux / macOS
source .venv/bin/activate
# Windows (PowerShell)
.venv\Scripts\Activate.ps1
Der Befehl python3 -m venv .venv erstellt das Verzeichnis .venv im Projekt. Den Namen .venv zu verwenden ist eine weit verbreitete Konvention, und die meisten IDEs erkennen ihn automatisch. Nach der Aktivierung hat der Shell-Prompt das Präfix (.venv). Die Befehle python und pip zeigen jetzt auf die venv.
Um die venv zu beenden, führe aus:
deactivate
2.2. Überprüfe, welchen Interpreter du nutzt
Das ist ein Schritt, den viele übersehen, und er ist die Quelle vieler Fehler wie “ich habe es installiert, aber ich bekomme immer noch ModuleNotFoundError”:
which python
python -c "import sys; print(sys.prefix); print(sys.prefix != sys.base_prefix)"
/home/lan/shopcart/.venv/bin/python
/home/lan/shopcart/.venv
True
sys.prefix ist das Wurzelverzeichnis der laufenden Umgebung. sys.base_prefix ist das Verzeichnis der ursprünglichen Python-Installation. Unterschiedliche Werte bedeuten, dass du in einer venv bist.
Hinweis: Commit nicht das Verzeichnis
.venvzu Git. Füge.venv/zu.gitignorehinzu. Die venv hängt von absoluten Pfaden und dem Betriebssystem ab, daher wird sie beim Kopieren auf eine andere Maschine normalerweise beschädigt. Die richtige Vorgehensweise ist, die Abhängigkeitsdatei zu committen und dann die venv auf der neuen Maschine neu zu erstellen.
3. Pakete mit pip verwalten
pip ist das Standard-Installationsprogramm für Pakete und bezieht diese von PyPI.
3.1. Grundlegende Befehle
python -m pip install --upgrade pip
python -m pip install "requests>=2.31,<3"
python -m pip list
python -m pip show requests
python -m pip uninstall -y requests
Die Befehle bewirken folgendes:
install --upgrade pipaktualisiert pip selbst. Eine ältere Version von pip kannpyproject.tomlmanchmal nicht korrekt lesen."requests>=2.31,<3"ist ein version specifier. Du musst ihn in Anführungszeichen setzen, damit die Shell das<nicht als Umleitung interpretiert.listlistet installierte Pakete auf.showdruckt detaillierte Informationen aus, einschließlich Version, Installationsort und Abhängigkeiten.
3.2. Warum solltest du python -m pip statt pip verwenden?
Der Befehl pip kann auf ein anderes Python verweisen als der Befehl python, das du gerade nutzt. Das passiert häufig, wenn die Maschine mehrere Python-Versionen hat oder wenn du vergessen hast, die venv zu aktivieren. Der Befehl python -m pip führt immer pip des korrekten Interpreters aus, daher werden Pakete sicher in die Umgebung installiert, die du möchtest.
3.3. requirements.txt und pip freeze
Die traditionelle Methode zum Austausch einer Paketliste ist die Verwendung von requirements.txt:
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
certifi==2024.8.30
charset-normalizer==3.4.0
idna==3.10
requests==2.32.3
urllib3==2.2.3
pip freeze speichert alle Pakete in der venv mit genauen Versionen, einschließlich indirekter Abhängigkeiten wie urllib3. Diese Methode eignet sich, um eine Laufzeitumgebung zu reproduzieren. Allerdings wird nicht klar, welche Pakete du wirklich brauchst und welche nur als Abhängigkeiten mitgezogen werden. Sie beschreibt auch nicht dein Projekt: Name, Version, Python-Anforderungen. Das ist Aufgabe der pyproject.toml.
4. Projekt mit pyproject.toml deklarieren
pyproject.toml ist die Standard-Konfigurationsdatei für ein Python-Projekt. Sie ist in den Standards PEP 518, PEP 621 und verwandten PEPs definiert. Diese Datei ersetzt setup.py, setup.cfg und fasst die Konfiguration für viele Tools wie pytest oder ruff an einem Ort zusammen.
4.1. Projektstruktur
Wir verwenden das src layout, d.h. wir platzieren Code in src/. Diese Struktur hilft sicherzustellen, dass Tests immer auf das installierte Paket laufen, nicht versehentlich direkt vom Arbeitsverzeichnis importieren:
shopcart/
├── pyproject.toml
├── src/
│ └── shopcart/
│ ├── __init__.py
│ ├── order.py
│ └── cli.py
└── tests/
└── test_order.py
4.2. Komplette pyproject.toml Datei
[build-system]
requires = ["setuptools>=69"]
build-backend = "setuptools.build_meta"
[project]
name = "shopcart"
version = "0.1.0"
description = "Einfache Berechnung des Bestelltotals"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31,<3",
]
[project.optional-dependencies]
dev = [
"pytest>=8",
"ruff>=0.5",
]
[project.scripts]
shopcart = "shopcart.cli:main"
[tool.pytest.ini_options]
testpaths = ["tests"]
Die Bedeutung jedes Abschnitts:
[build-system]teilt pip mit, welche Tools zum Erstellen des Projekts benötigt werden. Hier ist es setuptools. Du kannst es durchhatchlingoderflit_coreersetzen, ohne den Rest zu ändern.[project]enthält Standard-Metadaten.requires-pythonverhindert, dass jemand das Projekt auf einer nicht unterstützten Python-Version installiert.dependenciessind die Pakete, die erforderlich sind, um das Projekt auszuführen. Hier solltest du Versionsbereiche angeben (>=2.31,<3) und nicht an genaue Versionen gebunden sein (==2.32.3).[project.optional-dependencies]gruppiert optionale Pakete. DiedevGruppe enthält Entwicklungswerkzeuge, die Endbenutzer nicht brauchen.[project.scripts]erstellt einenshopcartBefehl im Terminal. Dieser Befehl ruft diemainFunktion im Modulshopcart.cliauf.[tool.pytest.ini_options]ist die spezifische Konfiguration für pytest. Jedes Tool hat seinen eigenen Namespace[tool.<name>].
Mit src layout findet setuptools automatisch das Paket in src/, du brauchst also keine zusätzliche Deklaration.
4.3. Code des Pakets
# src/shopcart/order.py
from dataclasses import dataclass, field
from decimal import Decimal
@dataclass(frozen=True)
class OrderLine:
product: str
unit_price: Decimal
quantity: int
@dataclass
class Order:
customer: str
lines: list[OrderLine] = field(default_factory=list)
def add(self, product: str, unit_price: str, quantity: int = 1) -> None:
if quantity <= 0:
raise ValueError("quantity muss größer als 0 sein")
self.lines.append(OrderLine(product, Decimal(unit_price), quantity))
def total(self) -> Decimal:
return sum((l.unit_price * l.quantity for l in self.lines), Decimal("0"))
Order speichert eine Liste von OrderLine. Für Preise verwenden wir Decimal, um Fehler bei Fließkommazahlen zu vermeiden, daher wird unit_price als Zeichenkette übergeben. Die total() Methode addiert die Gesamtbeträge jeder Zeile mit einem Startwert von Decimal("0").
# src/shopcart/cli.py
from shopcart.order import Order
def main() -> None:
order = Order(customer="Nguyễn Văn An")
order.add("Cà phê sữa", "29000", 2)
order.add("Bánh mì", "25000")
print(f"Kunde: {order.customer}")
print(f"Gesamtbetrag: {order.total():,} VND")
Die Datei src/shopcart/__init__.py kann leer bleiben.
4.4. Installation im editierbaren Modus
python -m pip install -e ".[dev]"
shopcart
Kunde: Nguyễn Văn An
Gesamtbetrag: 83.000 VND
Die Option -e (editable) verlinkt das Paket zum Quellcode, anstatt es in site-packages zu kopieren. Dadurch werden Änderungen an order.py sofort wirksam, ohne erneut zu installieren. Der Teil ".[dev]" bedeutet, das Projekt im aktuellen Verzeichnis mit der dev Gruppe zu installieren, sodass pytest und ruff ebenfalls installiert werden. Der Befehl shopcart ist im Terminal verfügbar dank der Deklaration in [project.scripts].
Hinweis: Einige Shells wie zsh interpretieren
[...]als Glob-Muster. Daher solltest du".[dev]"immer in Anführungszeichen setzen.
5. Tests schreiben und ausführen
# tests/test_order.py
from decimal import Decimal
import pytest
from shopcart.order import Order
def test_total_sums_all_lines():
order = Order(customer="Trần Thị Bình")
order.add("Trà đào", "35000", 2)
order.add("Bánh flan", "15000", 3)
assert order.total() == Decimal("115000")
def test_empty_order_has_zero_total():
assert Order(customer="Lê Minh").total() == Decimal("0")
def test_add_rejects_non_positive_quantity():
order = Order(customer="Phạm Hà")
with pytest.raises(ValueError, match="quantity"):
order.add("Nước suối", "10000", 0)
python -m pytest -q
... [100%]
3 passed in 0.02s
Tests importieren shopcart.order als installiertes Paket. Dies ist der Grund, warum du den pip install -e Schritt von oben brauchst. Wenn nicht installiert, wird das src layout einen ModuleNotFoundError auslösen, und dieser Fehler ist beabsichtigt: er zwingt dich, genau das zu testen, was Benutzer installieren werden. Der dritte Test nutzt pytest.raises, um zu prüfen, dass ungültige Mengen mit einer Nachricht abgelehnt werden, die “quantity” enthält.
6. Versionskontrolle: pyproject.toml oder requirements.txt?
Diese beiden Dateien schließen sich nicht gegenseitig aus. Sie dienen zwei unterschiedlichen Zwecken:
pyproject.toml |
Sperrdatei (requirements.txt mit fest gepinnten Versionen) |
|
|---|---|---|
| Zweck | Beschreibt, was das Projekt braucht | Reproduziert exakt eine Umgebung |
| Versionen | Bereiche (>=2.31,<3) |
Fest (==2.32.3) |
| Verwendet für | Bibliotheken und Anwendungen | Anwendungen in Produktion (Server, Docker) |
Bei einer Bibliothek, die auf PyPI veröffentlicht wird, brauchst du nur pyproject.toml. Wenn du Versionen fest pinnen würdest, würden Benutzer deiner Bibliothek Konflikte haben.
Bei einer Anwendung, die auf einem Server läuft, solltest du zusätzlich eine Sperrdatei haben, damit jeder Deployment exakt dieselbe Versionsmenge installiert:
python -m pip install -e .
python -m pip freeze --exclude-editable > requirements.lock
Bei der Bereitstellung installierst du zuerst aus der Sperrdatei, dann das Projekt selbst, um pip davon abzuhalten, Abhängigkeitsversionen automatisch zu aktualisieren:
python -m pip install -r requirements.lock
python -m pip install --no-deps .
Wenn du den Sperr-Prozess vereinfachen möchtest, kannst du pip-tools verwenden (Befehl pip-compile pyproject.toml) oder uv. Beide lesen direkt pyproject.toml und generieren eine Sperrdatei mit Hash-Werten.
7. Häufige Fehler
- Pakete mit
sudo pip installinstallieren. Dieser Befehl überschreibt System-Pakete und kann Systemtools beschädigen. Viele neue Linux-Distributionen blockieren dies sogar mit der Fehlermeldungexternally-managed-environment. Die Lösung besteht darin, immer eine venv zu verwenden. - venv nicht aktivieren, bevor man installiert oder ausführt. Du kannst das mit
which pythonwie in Abschnitt 2.2 überprüfen, oder den Python direkt aufrufen:.venv/bin/python -m pip .... - venv mit einer Python-Version erstellen und mit einer anderen ausführen. venv ist eng an den Interpreter gebunden, der sie erstellt hat. Wenn du Python aktualisierst (z.B. von 3.11 auf 3.12), lösche
.venvund erstelle sie neu. - Projektverzeichnis verschieben oder umbenennen. Die Skripte in
.venv/binenthalten absolute Pfade und funktionieren nicht mehr nach dem Verschieben. Du musst die venv neu erstellen. - Alt
setup.pymitpyproject.tomlvermischen und Abhängigkeiten an beiden Stellen deklarieren. Wähle eine einzige Quelle der Wahrheit. Bei neuen Projekten ist das die Tabelle[project]. - Entwicklungswerkzeuge in
dependenciesaufnehmen.pytestundruffsollten inoptional-dependencies(oder[dependency-groups]wenn dein Tool das unterstützt), damit Endbenutzer sie nicht installieren müssen.
Hinweis: Verwende keine venv, wenn du nur ein eigenständiges CLI-Tool wie
blackoderhttpieauf dem ganzen System ausführen möchtest. In diesem Fall istpipxbesser: Es erstellt automatisch eine eigene venv für jedes Tool und fügt den Befehl zuPATHhinzu.
8. Fazit
Dieser Artikel hat drei Grundwerkzeuge für ein Python-Projekt behandelt:
python -m venv .venvisoliert Abhängigkeiten für jedes Projekt.python -m pipinstalliert Pakete in den korrekten Interpreter.pyproject.tomldeklariert Metadaten, Abhängigkeiten, optionale Gruppen, CLI-Befehle und Werkzeugkonfiguration in einer einzigen Datei.
Mit src layout, pip install -e ".[dev]" und pytest hast du ein sauberes, leicht zu testendes und einfach zu teilendes Projekt. Für Anwendungen, die stabil bereitgestellt werden sollen, ergänze pyproject.toml durch eine Sperrdatei, um sicherzustellen, dass jede Umgebung dieselbe Versionsmenge installiert.