Contents

Running a Compiled Python Script from C# Applications

This article will guide you through running a compiled Python script (.pyc file) from a C# application, leveraging the capabilities of both languages.


Introduction

The interoperability between different programming languages enables developers to leverage the best features of each language. One such powerful combination is using Python and C# together.

Note
I’m not a C# expert. This blogpost is purely based on my learning while I was trying to run a Python script from a C# application.

Prerequisites

  • Python Installation: Ensure Python is installed on your machine and that the Python executable is accessible via the system’s PATH. You can download Python from the official Python website.
  • C# Development Environment: You’ll need an environment for writing and running C# code. Visual Studio is a popular choice, but you can use any editor or IDE of your choice.

A Python Script

In order to keep it simple, we will be using the following Python script:

1
2
def Greet(name):
    return f"Hello, {name}!"

Save the file as greeter.py to follow along.

Before running a Python script from C#, you would typically compile it into a .pyc file. Here’s how you can compile your Python script:

  • Open a terminal or command prompt.
  • Run the following command:
1
python3 -m compileall greeter.py

This command will generate a directory named __pycache__ containing the compiled .pyc file. In our case:

1
2
3
4
5
➜ tree .
.
├── __pycache__
│   └── greeter.cpython-312.pyc
└── greeter.py

C# Application

We start off by creating a console application using the dotnet CLI:

1
2
mkdir netpy && cd netpy
dotnet new console

Next, we add the pythonnet package to the project:

1
dotnet add package pythonnet

Update the Program.cs file with the code below to run the compiled (.pyc) Python script (greeter.py):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using Python.Runtime;

// Specify the path to the Python shared library (DLL or .so file)
Runtime.PythonDLL = "/opt/homebrew/Cellar/[email protected]/3.12.4/Frameworks/Python.framework/Versions/3.12/lib/libpython3.12.dylib";

// Initialize the Python engine
PythonEngine.Initialize();

// Use a try-finally block to ensure proper cleanup
try
{
    // Acquire the GIL (Global Interpreter Lock)
    using (Py.GIL())
    {
        // Add path to __pycache__ directory
        dynamic sys = Py.Import("sys");
        sys.path.append("/Users/gaurav.gahlot/workspace/playground/netpy");

        // Import the compiled .pyc file
        dynamic greeter = Py.Import("greeter");

        // Call the Greet function
        string result = greeter.Greet("Alice");
        Console.WriteLine(result);
    }
}

finally
{
    // Necessary for proper serialization
    AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true);

    // Shutdown the Python runtime
    PythonEngine.Shutdown();
}

Detailed Explanation

  • Python.Runtime Initialization:
    • You must set Runtime.PythonDLL property or PYTHONNET_PYDLL environment variable starting with version 3.0, otherwise you will receive BadPythonDllException upon calling Initialize.
    • You can read more about the same in their documentation.
  • Initialize Python Engine:
    • PythonEngine.Initialize(): This initializes the Python engine, making Python functionalities available within the C# environment.
  • Using Python with GIL:
    • using (Py.GIL()): Ensures that the Global Interpreter Lock (GIL) is acquired, which is necessary for thread-safety when interacting with Python objects.
  • Modifying Python Path:
    • dynamic sys = Py.Import("sys") and sys.path.append("path"): Adds the __pycache__ directory to the Python path. This is where the compiled .pyc file resides.
  • Import and Execute:
    • dynamic greeter = Py.Import("greeter"): Imports the compiled Python script.
    • string result = greeter.Greet("Alice"): Calls the Greet function from the imported script and prints the result.
  • Clean Up:
    • AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true): Ensures proper serialization. You can learn more about it in this GitHub issue.
    • PythonEngine.Shutdown(): Properly shuts down the Python engine to clean up resources.

Running the Application

  • Ensure that you have saved the code changes in Program.cs.
  • Open your terminal and navigate to the directory containing Program.cs
  • Run the application using dotnet run:
1
2
3
workspace/playground/netpy via .NET 8.0.401
➜ dotnet run
Hello, Alice!

This will execute the Python script and print the output to the console.


Conclusion

Using Python.NET simplifies the process of integrating Python with C#. You can leverage the capabilities of Python directly from your C# applications, making it possible to use Python’s extensive libraries and simplicity alongside C#’s strong performance and robust framework.

I hope this helps you get started with running compiled Python scripts from C# applications smoothly.