Python und Rust für verbesserte Leistung verbinden
Olivia Novak
Dev Intern · Leapcell

Einleitung
Python, bekannt für seine Lesbarkeit und sein riesiges Ökosystem, stößt bei rechenintensiven Aufgaben häufig auf Leistungsschwächen. Ob bei der Datenverarbeitung, wissenschaftlichen Simulationen oder Echtzeitanwendungen – das Global Interpreter Lock (GIL) und seine interpretierte Natur können die Geschwindigkeit beeinträchtigen. Rust hingegen bietet unübertroffene Leistung, Speichersicherheit und Thread-Konkurrenzfähigkeit, was es zu einem idealen Kandidaten für die Optimierung kritischer Abschnitte von Python-Code macht. Dieser Artikel befasst sich damit, wie wir Rust-Code effektiv in Python-Anwendungen integrieren können, mit besonderem Fokus auf zwei prominente Bibliotheken: PyO3 und rust-cpython. Durch die Nutzung der Stärke von Rust können Python-Entwickler schnellere, zuverlässigere Anwendungen erstellen, ohne auf die von Python gebotene Bequemlichkeit zu verzichten.
Kernkonzepte
Bevor wir uns mit den Besonderheiten von PyO3 und rust-cpython befassen, klären wir einige Kernkonzepte, die für das Verständnis dieser sprachübergreifenden Integration von entscheidender Bedeutung sind:
- Foreign Function Interface (FFI): FFI ist ein Mechanismus, mit dem ein in einer Programmiersprache geschriebenes Programm Routinen aufrufen oder Dienste nutzen kann, die in einer anderen Programmiersprache geschrieben wurden. Sowohl PyO3 als auch rust-cpython verlassen sich auf die C-API von Python, die deren FFI darstellt, um die Kommunikation zwischen Python und Rust zu ermöglichen.
- Python C API: Dies ist die C-Level-API, die vom Python-Interpreter bereitgestellt wird und es C- (und über FFI auch Rust-) Programmen ermöglicht, mit Python-Objekten zu interagieren, Python-Code auszuführen und den Python-Interpreter zu erweitern.
- Modul- und Funktions-Exportierung: Damit Rust-Code von Python aufgerufen werden kann, müssen wir Rust-Funktionen und -Strukturen als Python-Module oder aufrufbare Objekte exportieren. Dies erfordert eine sorgfältige Typzuordnung und die Einhaltung des Objektmodells von Python.
- Speicherverwaltung: Ein entscheidender Aspekt von FFI ist die sichere Verwaltung des Speichers über Sprachgrenzen hinweg. Das Rust-Eigentümersystem gewährleistet die Speichersicherheit innerhalb von Rust, aber bei der Interaktion mit Python müssen wir den Garbage-Collector von Python und die Konventionen der C-API beachten, um Speicherlecks oder Abstürze zu vermeiden.
Rust mit Python integrieren: PyO3 und rust-cpython
Sowohl PyO3 als auch rust-cpython sind leistungsstarke Werkzeuge zum Aufrufen von Rust-Code aus Python, bieten jedoch leicht unterschiedliche Ansätze und Funktionen.
PyO3: Ein High-Level und idiomatischer Ansatz
PyO3 ist eine beliebte und aktiv gepflegte Bibliothek, die High-Level-Bindings für das Schreiben von Python-Modulen in Rust bietet. Sie zielt darauf ab, ergonomisch und idiomatisch zu sein, damit sich Rust-Entwickler bei der Interaktion mit dem Python-Ökosystem wohlfühlen. PyO3 abstrahiert einen Großteil der Komplexität der Python-C-API und bietet Rust-ähnliche Makros und Traits zur Definition von Python-Modulen, Klassen und Funktionen.
Prinzip: PyO3 nutzt das Makrosystem von Rust, um den notwendigen C-API-Boilerplate-Code zu generieren. Sie definieren Rust-Funktionen und -Strukturen, annotieren sie mit PyO3-Makros, und PyO3 kümmert sich um die Konvertierung von Python-Typen in Rust-Typen und umgekehrt.
Beispiel: Erstellen wir eine einfache Rust-Funktion, die zwei Zahlen addiert und sie mit PyO3 für Python verfügbar macht.
Zuerst richten Sie Ihr Cargo.toml
ein:
[package] name = "my_rust_module" version = "0.1.0" edition = "2021" [lib] name = "my_rust_module" crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.21", features = ["extension-module"] }
Schreiben Sie dann Ihren Rust-Code in src/lib.rs
:
use pyo3::prelude::*; /// Formatiert die Summe zweier Zahlen als Zeichenkette. #[pyfunction] fn sum_as_string(a: usize, b: usize) -> PyResult<String> { Ok((a + b).to_string()) } /// Ein in Rust implementiertes Python-Modul. #[pymodule] fn my_rust_module(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?) ?; Ok(()) }
Um dies zu kompilieren, führen Sie maturin develop
im Stammverzeichnis Ihres Projekts aus (Sie müssen maturin
mit pip install maturin
installieren). Dies kompiliert den Rust-Code und installiert ihn als Python-Paket.
Jetzt können Sie es in Python verwenden:
import my_rust_module result = my_rust_module.sum_as_string(5, 7) print(f"Die Summe ist: {result}") # Ausgabe: Die Summe ist: 12 assert result == "12"
Anwendungsszenarien: PyO3 eignet sich hervorragend zum Erstellen neuer Python-Module in Rust, zur Optimierung von leistungskritischen Schleifen, zur Integration bestehender Rust-Bibliotheken in Python und zur Handhabung komplexer Datenstrukturen an der Schnittstelle. Sein ergonomisches Design macht es sowohl für einfache Funktionen als auch für komplexe Klassenhierarchien geeignet.
rust-cpython: Ein Low-Level, expliziter Ansatz
rust-cpython
bietet einen direkteren und Low-Level-Wrapper um die Python-C-API. Obwohl es ein tieferes Verständnis des internen Objektmodells von Python und der C-API erfordert, bietet es eine feinere Kontrolle über die Interaktion. Es ist weniger meinungsbildend als PyO3 und gibt Ihnen einen direkteren Zugriff auf die rohen Python-Objekte.
Prinzip: rust-cpython
stellt Rust-Typen bereit, die die C-API-Objekte von Python widerspiegeln (z. B. PyObject
, PyDict
, PyList
). Sie verwalten manuell die Referenzzählung und Typkonvertierungen und bieten so eine explizite Kontrolle über Speicherverwaltung und Objektinteraktion.
Beispiel: Implementieren wir die Funktion sum_as_string
mit rust-cpython
neu.
Aktualisieren Sie zuerst Cargo.toml
:
[package] name = "my_rust_module_cpython" version = "0.1.0" edition = "2021" [lib] name = "my_rust_module_cpython" crate-type = ["cdylib"] [dependencies] cpython = "0.7" # Oder neuere kompatible Version
Schreiben Sie dann Ihren Rust-Code in src/lib.rs
:
extern crate cpython; use cpython::{PyResult, Python, PyModule, PyDict, PyObject, ToPyObject}; fn sum_as_string_cpython(py: Python, a: PyObject, b: PyObject) -> PyResult<String> { let a_int: usize = a.extract(py)?; let b_int: usize = b.extract(py)?; Ok((a_int + b_int).to_string()) } // Spezielles Makro, das von cpython bereitgestellt wird, um Module zu exportieren py_module_initializer!(my_rust_module_cpython, initmy_rust_module_cpython, PyInit_my_rust_module_cpython, |py, m| { m.add(py, "__doc__", "Ein in Rust mit rust-cpython implementiertes Python-Modul.")?; m.add_wrapped(py, "sum_as_string", sum_as_string_cpython)?; Ok(()) });
Um dies zu kompilieren, verwenden Sie wie zuvor maturin develop
.
Jetzt in Python:
import my_rust_module_cpython result = my_rust_module_cpython.sum_as_string(10, 20) print(f"Die Summe ist: {result}") # Ausgabe: Die Summe ist: 30 assert result == "30"
Anwendungsszenarien: rust-cpython
eignet sich für Szenarien, in denen Sie eine detaillierte Kontrolle über Python-Objekte benötigen, vielleicht für leistungskritische Interaktionen auf sehr niedriger Ebene oder bei der direkten Schnittstelle zu internen Python-Strukturen. Es könnte von Entwicklern bevorzugt werden, die mit der Python-C-API bereits sehr vertraut sind und eine engere Abbildung in Rust wünschen.
Vergleich von Pyo3 vs. Rust-cpython
Merkmal | PyO3 | rust-cpython |
---|---|---|
Benutzerfreundlichkeit | High-Level, ergonomisch, Rust-idiomatisch | Low-Level, expliziter, C-API-zentriert |
Abstraktion | Hohe Abstraktion über die Python C API | Niedrige Abstraktion, direkte C API Wraps |
Makros | Verwendet Macros intensiv für das Binden | Weniger auf Makros angewiesen, manueller |
Fehlerbehandlung | Rusts Result und PyErr | PyResult und manuelle Fehlerbehebung |
Typkonvertierung | Meist automatisch mit FromPyObject /ToPyObject | Explizite Konvertierungen mittels extract /to_py_object |
Community/Aktivität | Sehr aktiv, modern | Weniger aktiv, aber stabil |
Fazit
Sowohl PyO3 als auch rust-cpython bieten effektive Wege zur Integration von Hochleistungs-Rust-Code in Python-Anwendungen, was es Entwicklern ermöglicht, den Interpretations-Overhead von Python für kritische Aufgaben zu überwinden. PyO3 zeichnet sich durch sein ergonomisches Design und seine High-Level-Abstraktionen aus und ist damit die erste Wahl für die meisten Benutzer, die ein produktives und idiomatisches Rust-Erlebnis suchen. Rust-cpython bietet feinere Kontrolle für diejenigen, die tiefer in die Python-C-API eintauchen müssen. Durch die Nutzung dieser leistungsstarken Werkzeuge können Entwickler erhebliche Leistungssteigerungen erzielen und sicherstellen, dass ihre Python-Anwendungen für anspruchsvolle Workloads robust und reaktionsschnell bleiben.