Python in .NET – Integration mit Python.NET
.NET, Python und KI, Teil 1
Künstliche Intelligenz hält längst Einzug in produktive .NET-Anwendungen. Doch wie lassen sich in Python generierte Modelle und ML-Frameworks effizient in C#-Projekte integrieren? Der Artikel „Effizienter Brückenschlag“ aus der dotnetpro-Ausgabe 2/2026 [1] hat bereits eine Einführung in diese Thematik gegeben. Hier soll nun der Teilaspekt der Integration mittels Python.NET vertieft werden.
Python.NET besteht aus zwei Komponenten: dem C#-Wrapper (Python.Runtime.dll) und dem CPython-Interpreter. Zur Nutzung in .NET-Projekten fügen Sie das NuGet-Paket Python.Runtime hinzu. Das können Sie in einem aktiven .NET-Projekt zum Beispiel über den NuGet-Paketmanager (Bild 1) erledigen:
dotnet add package Python.Runtime
Integration von Python.NET via NuGet in ein .NET-Projekt (Bild 1)
AutorAuf der Python-Seite installieren Sie das PyPI-Paket mit:
pip install pythonnet
Achten Sie darauf, dass die Python-Version kompatibel ist: Python.NET 3.x unterstützt derzeit CPython bis Version 3.13. Ab Version 3.0 muss explizit auf die richtige Python-DLL verwiesen werden: Setzen Sie etwa Runtime.PythonDLL = "python38.dll" (Windows) oder PythonEngine.PythonHome/PYTHONHOME auf das Installationsverzeichnis. Für eine 64-Bit-Anwendung benötigen Sie die 64-Bit-Python-Version.
Python aus C# aufrufen (Einbettung)
In C# initialisieren Sie die Python-Engine und rufen Python-Module wie folgt auf:
using Python.Runtime;
static void Main(string[] args)
{
// Python-Interpreter initialisieren
PythonEngine.Initialize();
// Alle Python-Aufrufe in einem using-Block mit GIL
using (Py.GIL())
{
// Beispiel: Unser Python-Script heißt "pythonExample.py"
dynamic mod = Py.Import("pythonExample"); // Importiert das Python-Modul
dynamic calc = mod.Calculator(); // Erstellt Instanz der Klasse Calculator
int result = calc.Add(7, 9); // Ruft die Methode Add auf
Console.WriteLine($"Ergebnis: {result}");
}
PythonEngine.Shutdown(); // Interpreter ordentlich herunterfahren
}
Erläuterung: Zuerst wird PythonEngine.Initialize() aufgerufen. Danach nutzen wir using (Py.GIL()) { … }, um den Zugriff auf den Python-Interpreter zu halten. Innerhalb der using-Anweisung importieren wir das Python-Skript (pythonExample.py) über Py.Import("pythonExample"). Python-Objekte sind hier vom dynamischen Typ dynamic, was die Funktionsaufrufe vereinfacht. Die in Python berechneten Ergebnisse können C#-Variablen zugewiesen werden (zum Beispiel wird result zu int gecastet). Abschließend rufen wir PythonEngine.Shutdown() auf, um Ressourcen freizugeben. Der Datenfluss ist in Bild 2 dargestellt.
Flussdiagramm des Aufrufs zwischen .NET (C#) und Python über Python.NET (Bild 2)
AutorDiese Einbettung ermöglicht den direkten Zugriff auf beliebige Python-Bibliotheken, etwa NumPy, Scikit-learn und viele weitere. Auch dazu ein Beispiel (Prinzip):
using (Py.GIL())
{
dynamic np = Py.Import("numpy");
double c = (double)np.cos(np.pi * 2);
Console.WriteLine(c);
}
Soll pythonExample.py nicht im Projektverzeichnis liegen, können Sie dynamic sys = Py.Import("sys"); sys.path.append("…") verwenden, um den Suchpfad anzupassen. Beachten Sie, dass Python-Code in .NET nicht von der CLR verifiziert wird und daher nur vertrauenswürdiger Code ausgeführt werden sollte.
C#-Code aus Python aufrufen
Python.NET ermöglicht es auch, .NET-Assemblies in Python zu verwenden, beispielsweise wie folgt:
import clr
clr.AddReference("MyDotNetLibrary") # DLL-Namen ohne ".dll"
from MyNamespace import MyClass # C#-Klasse importieren
obj = MyClass() # Instanz erzeugen
print(obj.MyMethod(5)) # Methode aufrufen
Hier übernimmt das clr-Modul die Brücke: clr.AddReference lädt eine .NET-Assembly.
from pythonnet import load
load("coreclr")
Danach steht der gesamte .NET-Namensraum wie ein Python-Paket zur Verfügung. Diese Richtung („.NET aus Python“) wird oft genutzt, um .NET-Funktionen in Python-Skripten zu nutzen, sofern man beispielsweise ein bestehendes C#-Modul verwenden möchte.
Fallstricke, Debugging und Performance
Häufige Stolpersteine sind:
- das Vergessen des GIL, das heißt, außerhalb von using (Py.GIL()) darf kein Python-Code ausgeführt werden
- Architektur-Mismatches (x86 versus x64)
- fehlende Python-Abhängigkeiten, das heißt, es muss die Runtime.PythonDLL gesetzt werden, zum Beispiel für die python310.dll
Um in Python zu debuggen, können Sie die Entwicklungsumgebungen beziehungsweise Editoren PyCharm, VS Code oder Visual Studio (mit Python-Tools) einsetzen. Setzen Sie zur Laufzeit zum Beispiel Breakpoints oder print-Anweisungen in Ihrem Python-Skript.
Interop-Aufrufe sind relativ „teuer“, das heißt, jeder Aufruf wird über den Bridge-Layer vermittelt. Die Nutzung von Python-Funktionen aus C# heraus ist deshalb deutlich weniger performant, als wenn man direkt C#-Code nutzen würde. Das ist insbesondere bei häufigen und wiederholenden Aufrufen (Verarbeitung in Schleifen) von Bedeutung. Der Overhead entsteht durch Dynamik, GIL-Akquise und Marshalling, wobei Marshalling das Umwandeln von strukturierten oder elementaren Daten in ein Format bedeutet, das die Übermittlung an andere Prozesse oder Programme ermöglicht.
Ein praktikabler Ansatz ist es daher, möglichst große Datenmengen oder Aufrufe zu bündeln (Batch-Query). Python-intensive Algorithmen sollten möglichst vollständig in Python stattfinden. Beachten Sie außerdem: Python.NET nutzt den CPython-Interpreter mit Global Interpreter Lock (GIL), daher laufen Python-Aufrufe stets seriell.
Beim Deployment gilt es sicherzustellen, dass die Python-Laufzeitumgebung auf dem Zielsystem vorhanden ist (oder eingebettet wird). Üblich ist, eine konkrete Python-Installation mitzuliefern und die Systemvariable PythonEngine.PythonHome auf dieses Installationsverzeichnis zu setzen. Achten Sie auf alle benötigten DLLs (Windows) und die korrekte Architektur (32/64 Bit). Abschließend testen Sie die Verteilung im Zielsystem.
Vergleich mit Alternativen
Python.NET ist zwar der flexibelste und leistungsfähigste Ansatz zur direkten Integration von Python in .NET-Anwendungen, jedoch keineswegs die einzige Möglichkeit, Python-Funktionalität aus C# heraus zu nutzen. Je nach Einsatzszenario können auch alternative Integrationsstrategien sinnvoll sein. Eine naheliegende Alternative ist IronPython, eine vollständig in .NET implementierte Python-Laufzeitumgebung. Da IronPython ohne nativen CPython-Interpreter arbeitet, entfällt die Abhängigkeit von einer externen Python-Installation. Zudem besitzt IronPython keinen Global Interpreter Lock (GIL), wodurch echte parallele Ausführung mehrerer Threads möglich ist. Das ist ein Vorteil gegenüber CPython-basierten Lösungen wie Python.NET. Allerdings hat dieser Ansatz Einschränkungen: IronPython unterstützt nur ältere Python-Versionen und kann keine nativen C-Erweiterungen laden. Damit entfallen viele zentrale Bibliotheken des modernen Python-Ökosystems. Hinzu kommt, dass die Weiterentwicklung des Projekts deutlich langsamer verläuft als bei Python.NET.
Ein weiterer pragmatischer Ansatz besteht darin, Python-Skripte als externen Prozess aus einer .NET-Anwendung heraus zu starten, beispielsweise über Process.Start("python", "script.py"). Diese Methode ist einfach umzusetzen und nutzt eine vollständig normale Python-Installation, ohne spezielle Interop-Bibliotheken zu benötigen. Dadurch eignet sich der Ansatz insbesondere für lose gekoppelte Batch- oder Automatisierungsszenarien. Der Nachteil liegt jedoch im vergleichsweise hohen Overhead: Jeder Aufruf startet einen neuen Prozess, was zusätzliche Latenz verursacht und den Ansatz für häufige oder echtzeitkritische Aufrufe ungeeignet macht. Zudem ist die Kommunikation zwischen .NET und Python hierbei deutlich umständlicher, da Daten typischerweise über Dateien, Kommandozeilenparameter oder Standard-Ein-/Ausgabe übertragen werden müssen.
Um die Startkosten eines Subprozess-Ansatzes zu reduzieren, kann ein persistenter Python-Prozess eingesetzt werden, der dauerhaft im Hintergrund läuft und über Standard-Ein-/Ausgabe oder Sockets mit der .NET-Anwendung kommuniziert. Dieses Muster wird häufig als „Python as a Service“ bezeichnet und eignet sich besonders für performantere Integrationsszenarien, bei denen dennoch eine Prozessentkopplung gewünscht ist. Subprozess-Lösungen sind eine pragmatische Wahl für lose gekoppelte Skriptausführung oder Batch-Verarbeitung. Python.NET bleibt hingegen die beste Option, wenn der vollständige Zugriff auf das moderne CPython-Ökosystem erforderlich ist und Python-Code eng in die .NET-Anwendung integriert werden soll. Es dürfte der passende Ansatz für die meisten Anforderungen sein, denn es sind vollständige Python-Bibliotheken verfügbar.
Fazit
Mit Python.NET lassen sich .NET- und Python-Welten praktisch verschmelzen. Einmal korrekt eingerichtet, können Sie in C# auf beliebige Python-Funktionen und -Pakete zugreifen und so zum Beispiel ML-Modelle in bestehende .NET-Anwendungen einbetten. Die größten Hürden sind Konfigurationsdetails (Python-Pfad, GIL-Management) und Performance-Kosten, die aber durch sinnvolles Design kompensiert werden können. Die Integration eröffnet C#-Entwicklern viele Wege, von der reichen Python-Ökonomie zu profitieren.
[1] Veikko Krypczyk, Effizienter Brückenschlag, dotnetpro 2/2026, Seite 40 ff.