Vom Skriptordner zur installierbaren Bibliothek
Code, der in einem Skriptordner lebt, funktioniert auf einer Maschine für eine Person, bis jemand anderes ihn braucht oder Sie ihn in einer anderen Umgebung brauchen. Paketieren macht Ihren Code zu einer Bibliothek mit Namen, Version und deklarierten Abhängigkeiten, die jeder mit pip install installieren kann. So teilt ein Team eine Bewertungsbibliothek, ohne Dateien herumzukopieren, führen ein Recherche-Notebook und ein Produktionsjob exakt denselben Code aus, und ein Bugfix erreicht alle über eine neue Versionsnummer.
# Versions are numbers, not text: compare them as tuples
def parse_version(text):
parts = [int(part) for part in text.split(".")]
return tuple(parts + [0] * (3 - len(parts)))
print("1.10" > "1.9") # False as text, "1" < "9" decides it: wrong
print(parse_version("1.10") > parse_version("1.9")) # True as numbers (1, 10, 0) is newer than (1, 9, 0)
print(parse_version("2")) # (2, 0, 0)Aufbau, pyproject.toml und Build
Ein gängiges Layout legt den Code in src/yourlib/ (mit einer __init__.py), die Tests in tests/ und eine pyproject.toml im Wurzelverzeichnis ab. Diese eine Datei nennt das Build-Werkzeug (die Tabelle [build-system]) und beschreibt das Projekt: name, version, eine Beschreibung, die unterstützten Python-Versionen und die dependencies. pip install -e . installiert den Ordner während der Entwicklung im editierbaren Modus, sodass Änderungen sofort wirken; python -m build erzeugt ein Wheel (.whl, installationsbereit) und ein sdist (Quellarchiv) in dist/. Legen Sie die Version nur an einer Stelle fest und machen Sie sie bei Bedarf als yourlib.__version__ verfügbar.
# A minimal pyproject.toml, generated and then read back with tomllib to prove it is valid
import tomllib
def pyproject_toml(name, version, dependencies):
lines = ["[build-system]", 'requires = ["setuptools>=68"]', 'build-backend = "setuptools.build_meta"', "",
"[project]", f'name = "{name}"', f'version = "{version}"']
if dependencies:
lines += ["dependencies = ["] + [f' "{d}",' for d in dependencies] + ["]"]
else:
lines.append("dependencies = []")
return "\n".join(lines) + "\n"
text = pyproject_toml("qcpricing", "0.3.0", ["numpy>=1.26", "pandas>=2"])
data = tomllib.loads(text)
print(data["project"]["name"], data["project"]["version"]) # qcpricing 0.3.0
print(data["project"]["dependencies"]) # ['numpy>=1.26', 'pandas>=2']Versionen und Versionsangaben
Die meisten Bibliotheken folgen der semantischen Versionierung: MAJOR.MINOR.PATCH. Ein Bugfix erhöht den Patch (1.4.2 auf 1.4.3), ein abwärtskompatibles Feature erhöht Minor und setzt Patch zurück (1.5.0), und eine inkompatible Änderung erhöht Major (2.0.0). Abhängigkeiten werden mit Spezifizierern deklariert: numpy>=1.26,<3 akzeptiert jedes NumPy ab 1.26 bis ausschließlich 3. Seien Sie so locker, wie Sie ehrlich sein können: eine Untergrenze für die älteste getestete Version, eine Obergrenze nur für eine Hauptversion, von der Sie wissen, dass sie Sie bricht. Exakte Versionen (==) festzunageln gehört in die Lock-Datei einer Anwendung, nicht in eine Bibliothek, die andere installieren.
# Bumping a version and checking a requirement, with versions compared as tuples
import operator
def parse_version(text):
parts = [int(part) for part in text.split(".")]
return tuple(parts + [0] * (3 - len(parts)))
def bump_version(version, part):
major, minor, patch = parse_version(version)
return {"major": f"{major + 1}.0.0", "minor": f"{major}.{minor + 1}.0", "patch": f"{major}.{minor}.{patch + 1}"}[part]
OPS = {">=": operator.ge, "<": operator.lt, "==": operator.eq}
def satisfies(version, specifier):
for clause in specifier.split(","):
symbol = next(op for op in (">=", "==", "<") if clause.startswith(op))
if not OPS[symbol](parse_version(version), parse_version(clause[len(symbol):])):
return False
return True
print(bump_version("1.4.2", "minor")) # 1.5.0
print(satisfies("1.26.4", ">=1.26,<3"), satisfies("3.0.0", ">=1.26,<3")) # True False# One place for the version: read it from the source instead of repeating it in several files
import re
init_py = """__version__ = "0.3.0"
from .pricing import price
"""
match = re.search(r'^__version__ = "([^"]+)"', init_py, re.MULTILINE)
version = match.group(1)
print(version) # 0.3.0
print(tuple(int(part) for part in version.split("."))) # (0, 3, 0)- Paketieren gibt Code einen Namen, eine Version und deklarierte Abhängigkeiten, installierbar mit
pip install; entwickeln Sie mitpip install -e ., bauen Sie mitpython -m build. pyproject.tomlenthält das Build-System und die[project]-Metadaten; vergleichen Sie Versionen als Zahlentupel, nie als Text.- Nutzen Sie semantische Versionierung (
MAJOR.MINOR.PATCH) und lockere Spezifizierer (numpy>=1.26,<3); ändern Sie nie eine veröffentlichte Version und halten Sie Geheimnisse aus dem Paket.
Als Nächstes: CI/CD für Quant-Code, die Prüfungen bei jeder Änderung automatisch ausführen.
Lab: Was bewertet wird
- Versionen mit
parse_versionals Zahlen vergleichen25 Pkt. - Mit
bump_versiondie nächste Version freigeben25 Pkt. - Mit
satisfieseine Anforderung prüfen25 Pkt. - Das Paket mit
pyproject_tomlbeschreiben25 Pkt.