DEV Community

Cover image for Introducing DotNetPy: Python Interop for Modern .NET, Reimagined
Jung Hyun, Nam
Jung Hyun, Nam

Posted on

Introducing DotNetPy: Python Interop for Modern .NET, Reimagined

The Problem

.NET and Python are two of the most widely used platforms in enterprise software today, yet bringing them together has always been painful. Existing interop libraries either require heavy runtime dependencies, lack support for modern .NET features like Native AOT, or demand complex project configurations with Source Generators.

If you've ever wanted to leverage Python's rich data science and ML ecosystem from a C# application — without leaving your .NET toolchain — you know the friction.

DotNetPy is my answer to that problem. Version 0.5.0 is now available on NuGet.

What Is DotNetPy?

DotNetPy (pronounced dot-net-pie) is a .NET library for executing Python code directly from C#. It wraps the Python C API behind a clean, minimal interface.

Here's the entire "hello world":

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}");
Enter fullscreen mode Exit fullscreen mode

No separate .py files. No Source Generators. No complex setup.

Why Another Interop Library?

There are established options — pythonnet, CSnakes, IronPython. Each has strengths, but none was designed for where .NET is heading in 2025 and beyond. DotNetPy fills that gap with three design pillars:

1. Native AOT Support

DotNetPy is the only .NET-Python interop library that works with PublishAot=true. If you're building self-contained, ahead-of-time compiled applications, DotNetPy won't hold you back. Neither pythonnet nor CSnakes support this today.

2. File-based App Ready (.NET 10+)

.NET 10 introduces file-based apps — run a single .cs file with dotnet run script.cs, no project file required. DotNetPy is designed to work in this scenario from day one.

# No csproj needed
dotnet run my-analysis.cs
Enter fullscreen mode Exit fullscreen mode

This makes it ideal for scripting, prototyping, and lightweight automation tasks where spinning up a full project is overkill.

3. Declarative uv Integration

Managing Python environments from .NET has always been a manual, error-prone process. DotNetPy integrates with uv, the fast Python package manager, so you can declare your entire 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 packages

var executor = project.GetExecutor();
executor.Execute("import numpy as np; print(np.mean([1,2,3]))");
Enter fullscreen mode Exit fullscreen mode

One call to InitializeAsync() handles Python download, virtual environment creation, and dependency installation. No more "works on my machine" issues.

How Does It Compare?

Here's a quick comparison with the existing ecosystem:

DotNetPy pythonnet CSnakes IronPython
Native AOT
File-based Apps
Source Generator Required No No Yes No
uv Integration Built-in None Supported None
Bidirectional Calls C#→Py C#↔Py C#→Py C#↔Py
Learning Curve Very Low Medium High Low

DotNetPy deliberately focuses on the C# → Python direction. If you need Python calling back into .NET objects, pythonnet remains the right choice. DotNetPy is for scenarios where .NET is the host and Python is the tool.

Built-in Security

Executing dynamic code always carries risk. DotNetPy ships with a Roslyn analyzer that detects potential code injection at compile time.

// ❌ The analyzer flags this — user input as code
executor.Execute(userInput);

// ✅ Safe — user data passed as variables
executor.Execute(
    "result = sum(numbers)",
    new Dictionary<string, object?> { { "numbers", userNumbers } }
);
Enter fullscreen mode Exit fullscreen mode

This is a deliberate design choice: make the safe path easy and the dangerous path visible.

Features at a Glance

  • Automatic Python Discovery — cross-platform detection of installed Python distributions
  • Data Marshaling — pass .NET arrays, dictionaries, and primitives to Python and back
  • Variable Management — capture, check, delete, and clear Python variables from C#
  • Free-threaded Python Support — detects Python 3.13+ builds with --disable-gil
  • Thread Safety — automatic GIL management for safe concurrent access

Getting Started

Install from NuGet:

dotnet add package DotNetPy --version 0.5.0
Enter fullscreen mode Exit fullscreen mode

Initialize and run:

using DotNetPy;

Python.Initialize("/path/to/python313.dll");
var executor = Python.GetInstance();

var sum = executor.Evaluate("sum([1,2,3,4,5])")?.GetInt32();
Console.WriteLine(sum); // 15
Enter fullscreen mode Exit fullscreen mode

Or let DotNetPy find Python automatically:

Python.Initialize(PythonDiscovery.FindPython());
Enter fullscreen mode Exit fullscreen mode

Use Cases I'm Targeting

  • AI/ML integration: Call scikit-learn, PyTorch, or Hugging Face models from a .NET service
  • Data analysis scripts: Run pandas/numpy workloads inline without a separate Python service
  • Automation & scripting: Use .NET 10 file-based apps as Python-capable scripts
  • Legacy modernization: Gradually introduce Python capabilities into existing .NET applications

What's Next

The roadmap includes embeddable Python support on Windows (for simplified deployment) and specialized optimizations for AI and data science workflows. This is v0.5.0 — the API is stabilizing, and feedback at this stage is especially valuable.

Links


If you're a .NET developer who's been eyeing Python's ecosystem, give DotNetPy a try. Issues, stars, and feedback are all welcome.

Top comments (0)