CLI tool
pythonnet/pythonnet avatar
pythonnet/pythonnet

Python.NET, and the Python 3.8 DLL in its own instructions

Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers.

5,538 stars779 forksC#MIT

At a glance

What is it?
Python.NET bridges Python and the .NET CLR in both directions, but the defaults are conservative: Mono or .NET Framework unless you ask for CoreCLR, a top-level clr module in site-packages, and a DLL name in the documentation that predates the package's own Python floor.
Who is it for?
Python.NET fits a .NET shop that needs to call existing managed libraries from a Python service, or a C# application that needs to drive a Python model, and that can name its runtime explicitly. It does not fit a project on a Python outside the supported window, a Linux service that wants CoreCLR without .NET Core in a default location or the dotnet CLI on the PATH, or a team hoping for a C# API generator, since the example contract is dynamic.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • pythonnet/pythonnet5,538 ★ · +26
Star history
Repository2026-09-232026-11-14
pythonnet/pythonnet5.5k5.5k

26 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

clr.py installs as a top-level module in site-packages

The packaging section of the project manifest is short and it explains what actually ships. The setuptools configuration sets py-modules to clr, which means the package installs a single top-level module named clr into your environment, and the entry point in most example code is import clr. That module name is generic enough to collide with any other distribution that ships something called clr, and pip will not warn you about it. The runtime half is not in this package at all: the only dependency is clr_loader, pinned to a narrow range of 0.3.1 up to but not including 0.4.0. So a loader upgrade is a separate decision from a pythonnet upgrade, and an incompatible loader can be installed next to a perfectly good pythonnet. The default branch is master, the newest release is v3.2.1 from 2026-10-08, and the master branch took its last push on 2026-10-08.

Mono and .NET Framework are the defaults, CoreCLR is opt-in

Out of the box you are not on .NET Core. On Linux and macOS the default runtime is Mono, and on Windows it is the .NET Framework. Choosing CoreCLR takes two steps, and both are explicit. First there is a precondition: .NET Core has to be installed in a default location, or the dotnet CLI has to be on the PATH. Second there is a switch, either the PYTHONNET_RUNTIME environment variable set to coreclr or a direct call to pythonnet.load with the string coreclr, placed before the clr import.

python
from pythonnet import load
load("coreclr")

import clr

The ordering in that snippet is the whole point. The runtime has to be selected before clr is imported, because the import is what starts loading assemblies. The project also notes that details on loading different runtimes are left to the documentation, so the default-versus-explicit choice is one you have to make per environment rather than infer from a config file.

The DLL named in the instructions is a Python 3.8 DLL

The embedding side has a hard precondition, and the documentation around it has drifted. Starting with version 3.0 you must set the Runtime.PythonDLL property or the PYTHONNET_PYDLL environment variable before calling Initialize, and if you do not, calling Initialize raises BadPythonDllException, an internal exception derived from MissingMethodException. The typical values offered as examples are python38.dll on Windows, libpython3.8.dylib on Mac, and libpython3.8.so on most other Unix-like systems. Meanwhile the package metadata declares requires-python as 3.11 and above, with 3.16 excluded, and the classifiers list 3.11 through 3.15. So the interpreter floor and the documented library name describe different generations of Python, and on a machine running 3.13 you have to work out the right file name yourself rather than copying the one in the instructions.

python
import clr
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import Form

Every call into Python from C# goes inside a GIL block

The C# contract is uniform, and it asks for one thing repeatedly. All Python objects should be declared as dynamic, and all calls to Python should sit inside a using block that holds the GIL. If you plan to use Python objects from multiple threads you also call BeginAllowThreads after Initialize. Modules arrive through Py.Import, which returns a dynamic you then call like a function, and keyword arguments have two accepted spellings, one using Py.kw with a name and a value and one using a named argument syntax. The arithmetic rule is the sharpest edge in the whole API and is stated as a hard requirement rather than a caveat: mathematical operations involving Python and literal or managed types must have the Python object first.

csharp
        PythonEngine.Initialize();
        using (Py.GIL())
        {
            dynamic np = Py.Import("numpy");
            Console.WriteLine(np.cos(np.pi * 2));

The worked example prints 1.0 for that first line, which is the same result 2 * np.pi would give in Python. In C#, the README says 2 * np.pi does not work, because the literal is on the left.

The upper bound in requires-python closes the door on new interpreters

The supported window is spelled out as a range rather than a minimum: greater than or equal to 3.11 and less than 3.16. That upper bound is a real install-time decision. When a new interpreter release appears, an environment running it either resolves an older pythonnet that predates it or refuses to install, and the package manager will not silently pick something outside the range. The classifier list matches the range exactly, covering 3.11, 3.12, 3.13, 3.14, and 3.15, and the development status is recorded as 5, Production or Stable. The test configuration is strict in the same spirit, with xfail_strict enabled and the test paths pinned to the tests directory, so an expected failure that starts passing is treated as an error rather than ignored. Combined with a version file that the build reads dynamically, the manifest is unusually explicit about its own boundaries.

The bundled demos are Windows Forms programs

The example files give the fastest path into the project, and that path is Windows-shaped. There are five of them: a dynamic grid, a hello form, a splitter, and a wordpad, plus an xaml file that belongs to the grid example. The readme's assembly loading snippet points at System.Windows.Forms, which is the same library the hello form example needs. That makes sense given the default runtime story, since the .NET Framework is what ships with Windows, but it means the shortest documented route to a working demo is one that does not translate to the Mono or CoreCLR path without rework. The project does not ship a Linux equivalent in the examples it exposes. The documentation is where the cross-platform story is told, and it lives in the wiki together with the FAQ, troubleshooting notes, and a list of projects already using pythonnet, plus a mailing list and a chat room.

setup.py still carries a .NET Framework 4.6 switch

The build script keeps a path open for a runtime the manifest no longer targets. It defines an option spelled --net46-support, checks the command line for it, and removes it before handing the arguments on, alongside a Windows platform tag of win32.win_amd64. The dotnet-based build command takes a configuration value that defaults to release, and an inplace flag that puts compiled extensions into the source directory next to the pure Python modules instead of the build library. It also disables source link queries during the build, with a comment tying that to the build not being able to read the repository format yet and citing issue 1613. Around it sit a Justfile, a shell.nix for Nix, a uv lock file, a solution file using the newer slnx extension, and a PyInstaller entry point that supplies its own hook directories for frozen applications.

Editorial conclusion

Python.NET fits a .NET shop that needs to call existing managed libraries from a Python service, or a C# application that needs to drive a Python model, and that can name its runtime explicitly. It does not fit a project on a Python outside the supported window, a Linux service that wants CoreCLR without .NET Core in a default location or the dotnet CLI on the PATH, or a team hoping for a C# API generator, since the example contract is dynamic. Before adopting, read the wiki installation page, set PYTHONNET_RUNTIME deliberately, and check the clr_loader range against the runtime you plan to load.

Frequently asked questions

What is pythonnet and what does its clr module give you?

It is a package that lets Python code interact with the .NET Common Language Runtime and can also embed Python into a .NET application. CLR namespaces are treated essentially as Python packages, so import clr followed by from System import String is the entry point.

How do I install pythonnet?

The project links its PyPI page, a conda-forge package, and a NuGet package for the .NET side, while detailed installation instructions and system requirements live in the wiki. The package requires Python 3.11 or newer with 3.16 excluded, and depends on clr_loader between 0.3.1 and 0.4.0.

What does clr in Python do?

Importing clr makes CLR namespaces available as if they were Python packages. Assemblies are then loaded with the AddReference function in the clr module, for example clr.AddReference("System.Windows.Forms").

How do I use pythonnet with the CoreCLR runtime?

Set the PYTHONNET_RUNTIME environment variable to coreclr, or call pythonnet.load with coreclr before importing clr. .NET Core has to be installed in a default location, or the dotnet CLI has to be on the PATH, and Mono is the default on Linux and macOS while .NET Framework is the default on Windows.

How does the pythonnet clr module relate to the pythonnet package?

clr is the Python-side module the package installs, declared as a top-level module in the packaging configuration. pythonnet.load is the separate entry point used to select a runtime before that import happens.

Can I use Python inside a C# application with pythonnet?

Yes. Set the Runtime.PythonDLL property or the PYTHONNET_PYDLL environment variable, then call PythonEngine.Initialize. Without that setting you get BadPythonDllException, which is derived from MissingMethodException, when Initialize is called.

Official sources

  1. License: MIT
  2. Project website
  3. pythonnet/pythonnet on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pythonnet-pythonnet.svg)](https://hysenlabs.com/projects/pythonnet-pythonnet)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/pythonnet-pythonnet.svg)](https://hysenlabs.com/projects/pythonnet-pythonnet)

Was this useful?