Python mit Rust und PyO3 beschleunigen
Python besticht durch seine Prägnanz und die große Auswahl an Drittanbieter-Paketen, doch bei rechenintensiven Aufgaben, dem Parsen gigabyte-großer Daten oder der Validierung von Millionen Objekten wird der Interpreter langsamer. Bisher mussten Entwickler in solchen Fällen C-API-Erweiterungen manuell schreiben. Dieser Prozess ist arbeitsintensiv: Das Debuggen von Speicherlecks und die manuelle Referenzverwaltung nehmen schnell die Freude am Entwickeln.
Ein sichererer Weg появился несколько лет назад. Die PyO3-Bibliothek ermöglicht das Schreiben nativer Module in Rust bei gleichzeitiger Einhaltung strenger Speichersicherheitsgarantien und klarer Abstraktionen.
Wenn Sie den Pydantic v2-Validator, den orjson-JSON-Parser, das Polars-Framework oder OpenAIs Tiktoken-Tokenizer verwendet haben, haben Sie bereits Code ausgeführt, der mit PyO3 erstellt wurde.
Warum Rust und Python verknüpfen
Python eignet sich hervorragend für Geschäftslogik und schnelles Prototyping. Rust punktet dort, wo maximale Geschwindigkeit, geringer Ressourcenverbrauch und Parallelität gefragt sind.
PyO3 löst zwei Probleme:
- Outsourcing von Python-Anwendungsengpässen an separate Rust-Funktionen oder -Strukturen.
- Einbettung des Python-Interpreters in eine Rust-Binary zur Skriptausführung.
Am häufigsten wird das Projekt für das erste Szenario verwendet.
So schreiben Sie ein Modul in der Praxis
Für das Kompilieren von Rust-Code in ein Python-Paket empfehlen die Autoren das Tool maturin. Es übernimmt die Kompilierung und erstellt Wheel-Dateien.
Lassen Sie uns ein einfaches Modul erstellen, das zwei Zahlen addiert und das Ergebnis als String zurückgibt.
Zuerst richten wir die virtuelle Umgebung ein:
mkdir string_sum && cd string_sum
python -m venv .env
source .env/bin/activate
pip install maturin
maturin init --bindings pyo3
Die Rust-seitige Logik in der src/lib.rs-Datei:
use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
#[pymodule]
mod string_sum {
use super::*;
#[pymodule_export]
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
}
Die Makros #[pyfunction] und #[pymodule] generieren automatisch Code für die Python-C-API. Entwickler müssen PyObject-Typen nicht manuell in Rust-Strukturen und zurück konvertieren.
Starten wir den Build:
maturin develop
Jetzt kann die Funktion direkt aus Python aufgerufen werden:
import string_sum
result = string_sum.sum_as_string(5, 20)
print(result) # '25'
Wenn Sie die echte Leistung in der Produktion messen möchten, bauen Sie das Modul einfach mit dem Optimierungsflag: maturin develop --release.
Praxisbeispiele
In Pydantic V2 wurde die gesamte Typprüfungs- und Validierungslogik in einem separaten Paket pydantic-core in Rust umgeschrieben. Die Leistung stieg dramatisch an.
Die Polars-Bibliothek verdrängt Pandas bei der Arbeit mit großen Datensätzen dank paralleler Berechnungen in Rust. Der Benutzer schreibt vertrauten Python-Code, während die schwere Arbeit im kompilierten Modul erledigt wird.
Im Bereich des maschinellen Lernens wird PyO3 von Hugging Face im tokenizers-Paket und von OpenAI in tiktoken verwendet. Schnelles Text-Parsing vor dem Senden an das neuronale Netz reduziert die Service-Latenz erheblich.
Unterstützende Tools
Um PyO3 hat sich eine Reihe nützlicher Utilities gebildet:
maturineliminiert die Notwendigkeit, komplexe Konfigurationen für setup.py zu schreiben.rust-numpybindet NumPy-Arrays mit Rust, ohne Daten im Speicher zu kopieren.pyo3-async-runtimesverbindet asyncio mit der Tokio Async-Laufzeitumgebung.pythonizetransformiert Datenstrukturen mit Serde.
Fazit
Wenn Ihr Projekt einen Performance-Engpass aufweist, hilft PyO3, das Problem zu lösen, ohne auf C oder C++ umzusteigen. Die Hauptschwierigkeit liegt hier im Erlernen von Rust selbst. Das Verständnis von Ownership-Konzepten und Datentypen ist erforderlich, aber die Bibliothek übernimmt die gesamte Routinearbeit mit CPython.
Ähnliche Projekte