Modern, AOT-ready Python interop for .NET — verified against free-threaded CPython ✨
// .NET ↔ Python in 4 lines
var executor = Python.GetInstance();
var temperatures = new[] { 23.5, 19.2, 31.8, 27.4, 22.1 };
using var result = executor.ExecuteAndCapture(@"
import statistics
result = {'mean': statistics.mean(data), 'stdev': statistics.stdev(data)}
", new Dictionary<string, object?> { { "data", temperatures } });
Console.WriteLine($"Mean: {result?.GetDouble("mean"):F1}°C ± {result?.GetDouble("stdev"):F1}");✅ Native AOT | ✅ .NET 10 file-based apps | ✅ Free-threaded Python 3.13t/3.14t | ✅ Isolated executors | ✅ Declarative uv | ✅ Built-in security analyzer
DotNetPy (pronounced dot-net-pie) is a .NET library that executes Python code from C# with minimal boilerplate. It is designed for modern .NET scenarios — Native AOT, file-based apps, and the new free-threaded CPython builds — where deeper interop libraries can't go yet.
Free-threaded Python (PEP 703). DotNetPy has been audited against pythonnet PR #2721's risk categories and verified to run correctly on CPython 3.13t and 3.14t alongside the classic GIL build. The audit, fixes, and verification matrix live in
docs/FREETHREADED-AUDIT.md. For concurrent callers that need hard isolation,Python.CreateIsolated()gives each thread its own Python namespace.
| Document | Description |
|---|---|
| Usage Examples | Detailed code examples and patterns |
| Security Guide | Security considerations and safe usage |
| uv Integration | Declarative Python environment management |
| Performance | Thread safety, concurrency, and isolated executors |
| Free-threaded Audit | PEP 703 audit, fixes, and 3-way verification matrix |
| Comparison | Decision tree across DotNetPy / pythonnet / CSnakes / IronPython |
| Testing | Running integration tests |
| Code Samples | Runnable sample applications |
DotNetPy is built around four core principles:
Write Python code as strings within your C# code, with full control over execution and data flow. No separate Python files, no Source Generators, no complex setup — just define your Python logic inline and execute it.
// Define and execute Python declaratively from C#
using var result = executor.ExecuteAndCapture(@"
import statistics
result = {'mean': statistics.mean(data), 'stdev': statistics.stdev(data)}
", new Dictionary<string, object?> { { "data", myNumbers } });Designed from the ground up for modern .NET scenarios:
- File-based Apps (.NET 10+): Works perfectly with
dotnet run script.cs— no project file required - Native AOT: The only .NET-Python interop library that supports
PublishAot=true - Minimal Dependencies: No heavy runtime requirements
# Just run it — no csproj needed
dotnet run my-script.csDeclaratively manage Python environments using uv:
// Define your Python environment in C#
using var project = PythonProject.CreateBuilder()
.WithProjectName("my-analysis")
.WithPythonVersion(">=3.10")
.AddDependencies("numpy>=1.24.0", "pandas>=2.0.0")
.Build();
await project.InitializeAsync(); // Downloads Python, creates venv, installs packagesDotNetPy is audited against PEP 703 risk categories (see FREETHREADED-AUDIT.md) and runs identically on classic GIL builds and on python3.13t / python3.14t. For concurrent callers that need a private Python namespace — plugin systems, multi-tenant scripting, or parallel ML inference — Python.CreateIsolated() produces an executor with its own globals dict:
// Each thread gets its own Python namespace; same variable names cannot collide.
Parallel.For(0, 16, callerId =>
{
using var iso = Python.CreateIsolated();
iso.Execute($"seed = {callerId}");
using var result = iso.ExecuteAndCapture("result = seed * 2 + 1");
// 'seed' and 'result' belong to this caller alone.
});DotNetPy executes arbitrary Python code with the same privileges as the host .NET process. Never pass untrusted or user-provided input directly to execution methods.
// ❌ DANGEROUS: User input executed as code
executor.Execute(userInput); // Remote Code Execution vulnerability!
// ✅ SAFE: User data passed as variables, not code
executor.Execute("result = sum(numbers)", new Dictionary<string, object?> { { "numbers", userNumbers } });DotNetPy includes a built-in Roslyn analyzer that detects potential code injection at compile time.
- Automatic Python Discovery: Cross-platform automatic detection of installed Python distributions with configurable requirements (version, architecture).
- Runtime Information: Query and inspect the currently active Python runtime configuration.
- Execute Python Code: Run multi-line Python scripts.
- Evaluate Expressions: Directly evaluate single-line Python expressions and get the result.
- Data Marshaling:
- Pass complex .NET objects (like arrays and dictionaries) to Python.
- Convert Python objects (including dictionaries, lists, numbers, and strings) back into .NET types.
- Variable Management:
ExecuteAndCapture: Execute code and capture a specific variable (by convention,result) into a .NET object.CaptureVariable(s): Capture one or more global variables from the Python session after execution.DeleteVariable(s): Remove variables from the Python session.VariableExists: Check if a variable exists in the Python session.GetExistingVariables: Returns a list of variables that actually exist from a given list of variable names.ClearGlobals: Clear all global variables from the Python session.
- Free-threaded Python (PEP 703): Audited and verified against CPython 3.13t and 3.14t; runs identically alongside classic GIL builds. See
docs/FREETHREADED-AUDIT.md. - Isolated Executors:
Python.CreateIsolated()gives each caller its own Python namespace — plugin systems, multi-tenant scripting, and parallel ML inference without shared-state pollution. - Native C-API exports: Build the AOT-published
dotnetpy-native.dlland call DotNetPy from C/C++/Rust through plainextern "C"exports — seesamples/native-aot. - No Boilerplate: The library handles the complexities of the Python C API, providing a clean interface.
- .NET 8.0 or later.
- A Python installation (e.g., Python 3.13). You will need the path to the Python shared library (
pythonXX.dllon Windows,libpythonX.X.soon Linux). - (Optional) uv for declarative environment management.
To start using DotNetPy, you need to initialize the Python engine with the path to your Python library.
using DotNetPy;
// Path to your Python shared library
var pythonLibraryPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Programs", "Python", "Python313", "python313.dll");
// Initialize the Python engine
Python.Initialize(pythonLibraryPath);
// Get an executor instance
var executor = Python.GetInstance();// Evaluate expressions
var sum = executor.Evaluate("sum([1,2,3,4,5])")?.GetInt32(); // 15
// Execute scripts and capture results
using var result = executor.ExecuteAndCapture(@"
import math
result = {'sqrt': math.sqrt(16), 'pi': math.pi}
");
Console.WriteLine(result?.GetDouble("sqrt")); // 4
// Pass .NET data to Python
var numbers = new[] { 10, 20, 30 };
using var stats = executor.ExecuteAndCapture(@"
result = {'sum': sum(data), 'avg': sum(data)/len(data)}
", new Dictionary<string, object?> { { "data", numbers } });Wondering how DotNetPy compares to pythonnet or CSnakes? Check out our detailed comparison guide to understand the differences and choose the right tool for your needs.
DotNetPy ships two concurrency models. The default shared singleton (Python.GetInstance) serializes via the GIL on classic CPython and behaves the same under free-threaded builds — best for sequential or I/O-bound work where you want variables to persist across calls. For genuine parallelism, Python.CreateIsolated() returns an executor with its own namespace, which combined with free-threaded CPython (3.13t / 3.14t) lets independent callers run side-by-side without contention.
📖 Performance & Concurrency Details →
Declaratively manage Python environments using uv:
using DotNetPy;
using DotNetPy.Uv;
// Define your Python project declaratively
using var project = PythonProject.CreateBuilder()
.WithProjectName("my-data-analysis")
.WithPythonVersion(">=3.10")
.AddDependency("numpy", ">=1.24.0")
.AddDependency("pandas", ">=2.0.0")
.Build();
await project.InitializeAsync(); // Downloads Python, creates venv, installs packages
var executor = project.GetExecutor();
executor.Execute("import numpy as np; print(np.mean([1,2,3]))");Ready-to-run sample applications are available in the samples/ directory:
| Sample | Description |
|---|---|
| quickstart | Minimal example - .NET ↔ Python data flow |
| uv-integration | Comprehensive test with uv-managed Python |
| declarative-python | Declarative environment setup with PythonProjectBuilder |
| native-aot | Drive the AOT-published dotnetpy-native.dll through its C exports; doubles as a free-threaded Python smoke test |
# Run the quickstart sample
cd samples/quickstart
dotnet run quickstart.csThe following features are planned for future releases:
- ✅ Automatic Python Discovery (Completed): Cross-platform automatic detection and discovery of installed Python distributions, eliminating the need for manual library path configuration.
- ✅ Virtual Environment (venv) Support (Completed): Enhanced support for working with Python virtual environments, including automatic activation and package management via
LoadVirtualEnvironment()extension method. - ✅ uv Integration (Completed): Declarative Python environment management using the uv package manager with
PythonProjectandPythonProjectBuilderclasses. - ✅ Free-threaded Python (PEP 703) (Completed): Audited against pythonnet PR #2721's risk categories and verified on CPython 3.13t / 3.14t alongside the classic GIL build. See
docs/FREETHREADED-AUDIT.md. - ✅ Isolated Executors (Completed):
Python.CreateIsolated()produces per-namespace executors so concurrent callers don't share__main__globals — the recommended pattern under free-threaded Python. - Embeddable Python Support (Windows): Automatic setup and configuration of embeddable Python packages on Windows for simplified deployment scenarios.
- AI and Data Science Scenarios: Specialized support and optimizations for AI and data science workflows, including better integration with popular libraries like NumPy, Pandas, and machine learning frameworks.
This project is licensed under the Apache License 2.0. Please see the LICENSE.txt file for details.
