Running a Compiled Python Script from C# Applications

gauravgahlot

Gaurav Gahlot

Posted on September 15, 2024

Running a Compiled Python Script from C# Applications

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.

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.

asciicast

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:

def Greet(name):
    return f"Hello, {name}!"
Enter fullscreen mode Exit fullscreen mode

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:
python3 -m compileall greeter.py
Enter fullscreen mode Exit fullscreen mode

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

➜ tree .
.
├── __pycache__
│   └── greeter.cpython-312.pyc
└── greeter.py
Enter fullscreen mode Exit fullscreen mode

C# Application

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

mkdir netpy && cd netpy
dotnet new console
Enter fullscreen mode Exit fullscreen mode

Next, we add the pythonnet package to the project:

dotnet add package pythonnet
Enter fullscreen mode Exit fullscreen mode

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

using Python.Runtime;

// Specify the path to the Python shared library (DLL or .so file)
Runtime.PythonDLL = "/opt/homebrew/Cellar/python@3.12/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();
}
Enter fullscreen mode Exit fullscreen mode

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:
workspace/playground/netpy via .NET 8.0.401
➜ dotnet run
Hello, Alice!
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
gauravgahlot
Gaurav Gahlot

Posted on September 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related