Saturday, 24 February 2024

Using IronPython to execute Python code from .NET

Let's look at some code showing how to execute Python code from .NET using IronPython! IronPython provides support for Python scripts to run inside .NET and utilizes the Dynamic Language Runtime - DLR. The DLR together allows the caller to get dynamic typing and dynamic method dispatch, which is central in the dynamic languages such as Python. IronPython was first released in 2004, some 20 years ago. It has continued to evolve slowly and provides seamless integration into .NET ecosystem for Python developers. In this article, I will present some simple code that shows how you can run Python code inside a .NET 8 console application. We will load up some tuples in an array in some simple Python code, using IronPython. Tuples in Python Tuples in Python are immutable (such as in C#) and are defined using parentheses and comma-separated. This is the same as in C#, but Python had tuple support over 20 years before C#. We will have to add one Nuget package, the IronPython package, in a net8.0 application.

HelloIronPythonDemo1.csproj



<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="IronPython" Version="3.4.1" />
  </ItemGroup>

  <ItemGroup>
    <None Update="customers.py">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>



Consider the following array of tuples in Python :

customers.py

customers = [
	('Jenna', 42, 165),
	('Thor', 40, 174),
	('Christopher', 18, 170),
	('Liz', 16, 168),
 ]

Python code is very compact and you declare variables without specifying type such as in C#, Python uses a simple way of creating variables and while C# got support in C# 7 in 2017, Python has had support for tuples since its early days. In the Python 1.4 version, we find it documented here:
https://docs.python.org/release/1.4/tut/node37.html#SECTION00630000000000000000.
Bear in mind, this is way back in 1996, C# was over 20 years later with its tuple support. If you install IronPython, you get a terminal where you can enter Python code (plus more functionality with .NET) such as shown below, where tuples are created and tuples may be composed or 'packed' and also 'unpacked', which is called deconstructed in .NET tuples.
To execute code to retrieve this array of tuples, first create a ScriptEngine and then create a ScriptScope, which we will use to retrieve the Python-declared variable customers. We create a ScriptSource, where we use the ScriptEngine to load up either a string or a file. A dynamic variable will be used to get the array of tuples and we can loop through this array with a foreach loop and output its content.

Program.cs
 
 using IronPython.Hosting;
 using Microsoft.Scripting.Hosting;
 using static System.Console;   
 IronPythonDemo1.OutputSomeExternallyLoadedTuples();

 public class IronPythonDemo1
 {

    public static void OutputSomeExternallyLoadedTuples()
    {
        var engine = Python.CreateEngine();
        ScriptScope scope = engine.CreateScope();

        //ScriptSource source = engine.CreateScriptSourceFromString(tupleStatement);
        ScriptSource source = engine.CreateScriptSourceFromFile("customers.py");
        source.Execute(scope);
        dynamic customers = scope.GetVariable("customers");
        foreach (var customer in customers)
        {
            Console.WriteLine($"(Name = {StringExtensions.FixedLength(customer[0], 20)}, Age = {StringExtensions.FixedLength(customer[1].ToString(), 8)}, Height={StringExtensions.FixedLength(customer[2].ToString(), 8)})");
        }
    }

}
 
 
 
Documentation for named tuples are available here: https://docs.python.org/3/library/collections.html#collections.namedtuple Here is sample coding showing script that although it is more verbose, shows more readability of which field is which for a named tuple. In an ordinary tuple, you use indexes to retrieve the nth field (0-based). But with named tuples, you use a field name instead.
 
from collections import namedtuple

Customer = namedtuple('Customer', ['Name', 'Age', 'Height'])
 
customers2 =  [
    Customer(Name = 'Jenna', Age = 42, Height = 165),
    Customer(Name = 'Thor', Age = 38, Height = 174),
    Customer(Name = 'Christopher', Age = 42, Height = 170),
    Customer(Name = 'Liz', Age = 42, Height = 168),
 ]
   
for cust in customers2:
    print(f"{cust.Name} with a height of {cust.Height}(cm)")

This outputs:
 
Jenna with a height of 165(cm)
Thor with a height of 174(cm)
Christopher with a height of 170(cm)
Liz with a height of 168(cm)
When your tuple gets many fields, having this readability should reduce bugs. Also, if you add more fields to your tuple, you do not have to fix up indexes in your script. So code is a bit more verbose, but it is also more open for change and readable.

The FixedLength extension method is a simple method to output text to a fixed width.
 
 
 public static class StringExtensions
 {

    public static string FixedLength(this string input, int length, char paddingchar = ' ')
    {
        if (string.IsNullOrWhiteSpace(input))
        {
            return input;
        }
        if (input.Length > length)
            return input.Substring(0, length);
        else
            return input.PadRight(length, paddingchar);
    }

 } 
 

1 comment:

  1. Here is some Python code for deconstructing a tuple in a flexible way using the Python 'ignore/anonymous variable' operator, similar to 'discard' in C# !

    def myfunc():
    return 1,2,3

    a,b,c = myfunc()
    print (a,b,c)

    a,b,*_ = myfunc()
    print (a,b)

    a,*_ = myfunc()
    print(a)

    ReplyDelete