Running IronPython Scripts from a C# 4.0 Program

IronPython is a scripting language hosted on the .NET platform. This posts shows how you can use the Dynamic Language Runtime (DLR) and the new C# 4.0 dynamic keyword to call an IronPython script from a C# program.

Before going any further, it might be helpful to take a moment to explore the architecture that makes this technology possible. The key building block is the DLR, which sits on top of the .NET 4.0 CLR and provides the tooling for adding dynamic programs such as Python to the family of .NET languages. C# 4.0 adds the dynamic keyword and some related technologies to integrate support for dynamic binding into the language. The end result is that developers can arbitrary Python scripts directly from C# or other .NET languages.

Setting up a C# Project to Call IronPython

In two previous posts I described how to install and run a simple IronPython program:

If you don’t already have the latest version of IronPython installed or if you need a little refresher course in writing Python scripts, then you might want to glance at these posts. Otherwise, you can just keep reading to learn how to call Python from C#.

After the IronPython installation, its interpreter and its libraries will be installed in your Program Files directory, as shown in Listing 1. The interpreter is installed in ipy.exe, where ipy is pronounced “Eye-Pie.” The interpreter remains dormant when you call Python from C#, so our focus here should be on the assemblies shown in the second half of the listing, with a particular emphasis on IronPython.dll.

Listing 1: At the command prompt you can easily see the core files that form the basis for the IronPython installation.

 C:\Program Files (x86)\IronPython 2.6 CTP for .NET 4.0 Beta 2>dir *.exe *.dll
 Volume in drive C has no label.
 Volume Serial Number is 4C6B-EE0E

 Directory of C:\Program Files (x86)\IronPython 2.6 CTP for .NET 4.0 Beta 2

10/18/2009  01:47 PM            12,624 ipy.exe
10/18/2009  01:47 PM            12,624 ipyw.exe

 Directory of C:\Program Files (x86)\IronPython 2.6 CTP for .NET 4.0 Beta 2

10/18/2009  01:47 PM         1,433,424 IronPython.dll
10/18/2009  01:47 PM           435,536 IronPython.Modules.dll
10/18/2009  01:47 PM           875,856 Microsoft.Dynamic.dll
10/18/2009  01:47 PM            58,192 Microsoft.Scripting.Debugging.dll
10/18/2009  01:47 PM           159,056 Microsoft.Scripting.dll
               7 File(s)      2,987,312 bytes
               0 Dir(s)  16,881,614,848 bytes free

To run a Python script inside a C# program, you should include some or all of the libraries shown in Listing 1 in the References section of your project. To get started, choose File | New Project (Ctrl-Shift-N) from the Visual Studio 2010 menu and create a new console application. Then open the Solution Explorer (Ctrl-W, S) and go to the References section for your project. Right click and choose Add Reference. Take a moment to appreciate how quickly the Add References dialog appeared (the team worked hared on this performance improvement), and then select the Browse tab and navigate to the directory where IronPython resides. The path to the directory hosting IronPython will probably be similar to the one shown in Listing 1. For now, the simplest choice is to select all the assemblies found in that directory, as shown in Figure 1. If you wish to keep things simple, then you only need to select IronPython.dll, Microsoft.Dynamic.dll and Microsoft.Scripting.dll to compile and run the program shown in this example.

 Figure01

Figure 1: Using the Add References dialog to select the assemblies found in that directory.

When you are done, the IronPython libraries should appear in the References section of the Solution Explorer, as shown in Figure 2.

Figure02

Figure 2: The References node of the Solution Explorer for a C# program that calls an IronPython script.

Writing the Code for Calling IronPython

Now that you have your project set up correctly you need to do two things:

  1. Write or find a script containing Python code that you want to run
  2. Write C# code to call the script.

Listing 2 shows a very simple IronPython script that contains a single method called Simple() that writes a few strings to a console window. Save this script to a file called Test.py, and add it to your project, as shown in Figure 2. There are several ways to do this. One is to right click on your project in the Solution Explorer, and choose Add | New Item. Add a text file, and call it Test.py. Click on the new node when it is added to your project, and set its Copy to Output Directory property to Copy Always. If the Properties window is not visible, you can bring it up by selecting Ctrl-W, P.

Listings 3a and 3b show the C# code for calling the script, and Listing 4 shows the output from the C# program that hosts the script.

Listing 2: A Simple Python script stored in a file called Test.py. This code prints out some basic information about the environment in which Python is running.

 import sys

def Simple():
  print "Hello from Python"
   print "Call Dir(): "
    print dir()
 print "Print the Path: " 
   print sys.path

Listing 3a: A simple C# program for calling a Python script

Code Snippet

  1. using System;
  2. using IronPython.Hosting;
  3. using Microsoft.Scripting.Hosting;
  4.  
  5. public class dynamic_demo
  6. {
  7.     static void Main()
  8.     {
  9.         var ipy = Python.CreateRuntime();
  10.         dynamic test = ipy.UseFile("Test.py");
  11.         test.Simple();
  12.     }
  13. }

Listing 3b: The same C# program shown in Listing 3a, but this presentation is easier for you to block copy into your own version of the program.

 using System;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

public class dynamic_demo
{
    static void Main()
    {
        var ipy = Python.CreateRuntime();
        dynamic test = ipy.UseFile("Test.py");
        test.Simple();
    }
}

The code in Listing 2 is a very simple Python script. Before calling it from C#, you might want to see it in action. That way you can be sure the script works and you can verify what the script is supposed to do. To get started, block copy the code in Listing 2. Use the Windows Start Menu to open up the Python Console window and paste in the code. Finally, you should explicitly call the Simple() method, as shown in Figure 3. When looking at this screen shot, you can see the version of the Python Console on the first two lines. The next 8 lines show the code that I pasted into the console. Finally, you see where I typed in the call to Simple(), and after that the output from the script. Once again, this has nothing to do with calling the code from C#, it is just a way of verifying that you have a valid script, and of confirming how it should perform.

Figure03

Figure 3: A screenshot showing a run of the Python script that we will be calling from our C# program.

Now let’s look at the 3 lines of code used to call this script from a C# program. This code is shown in Listings 3a and 3b. It first creates an instance of the Python runtime, as shown in line 9:

 var ipy = Python.CreateRuntime();

The call to CreateRunTime loads or sets up the core Python libraries and the DLR code needed to to execute the Python script. The second line of code, found on line 10, calls UseFile to load our simple Python script into memory. Notice that the result of this call is a dynamic object. The dynamic  keyword is a new feature in C# 4.0 and Visual Studio 2010 which tells the compiler to resolve this particular call not at compile time, but at runtime.

It’s very important to understand that the dynamic keyword breaks the strong type checking that has always been a hallmark of C# development. There is no doubt that the ability to call dynamic objects is a useful and important feature. However, it should be used sparingly since it sidesteps the strict type checking that makes C# such a powerful and useful language.

The dynamic keyword is needed here because Python is an interpreted scripting language where calls are bound at run time, not at compile time. There is no simple or practical way to bind calls to Python at compile time because Python is designed to be a dynamic language which is resolved at runtime. Since C# is a strongly typed language it expects to resolve calls at compile time with strict type checking. In short, we are at an impasse. C# binds statically at compile time, Python dynamically at runtime. Something has to give, and the only valid solution was for C# to allow runtime binding for method calls.

It is important to grasp that calls into Python are not strictly type checked at compile time, and are therefore not guaranteed to succeed at runtime. Just because your program compiled without error does not mean that you have properly bound each call in your program. In this case we call the method named Simple() , which does in fact exist. As a result, the call succeeds, as shown by the output from the program found in Listing 4.

Listing 4: The output from the C# program which calls a Python script.

 Hello from Python
Call Dir():
[]
Print the Path:
['.', 'C:\\Users\\Charlie\\Documents\\SyncData\\Source\\ProjectsShared\\Python\\CallPython\\CallPython\\bin\\Debug\\Lib', 
'C:\\Users\\Charlie\\Documents\\SyncData\\Source\\ProjectsShared\\Python\\CallPython\\CallPython\\bin\\Debug\\DLLs']
Press any key to continue . . .
 

Listing 4 shows the good side of working with dynamic code. Now let’s take a moment to consider the dark side. Suppose we called a method on our dynamic test class that does not exist. For the sake of argument, let’s call the method NonexistentMethod(), which is shown in Listing 5.

Listing 5: This code contains a call to a method called NonexistentMethod() that does not exist in our Test.py file. As a result, the call will fail at runtime.

  var ipy = Python.CreateRuntime();
dynamic test = ipy.UseFile("Test.py");
test.Simple();            // Succeeds
test.NonexistentMethod(); // Fails

The code in Listing 5 contains a call to the method Simple() that will succeed, since the Python script shown in Listing 2 does contain a method called Simple() . However, the call to the method NonexistentMethod() will raise an exception at runtime. This happens because the call cannot be resolved at runtime. At compile time, however, the compiler knows nothing about the object called test. It makes no attempt to see whether or not the call will succeed. It simply takes your word for it and allows the compilation to succeed. At runtime, however, the error raises its head, as shown in Figure 4 and Listing 6.

Figure03a

Figure 4: The exception raised inside the IDE at runtime when you attempt to call a method that does not exist.

Listing 6: The details of the exception that you receive at runtime when you attempt to call a method that does not exist.

 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled
  Message='Microsoft.Scripting.Hosting.ScriptScope' does not contain a definition for 'NonexistentMethod'
  Source=Anonymously Hosted DynamicMethods Assembly
  StackTrace:
       at CallSite.Target(Closure , CallSite , Object )
       at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
       at dynamic_demo.Main() in C:\Users\Charlie\Documents\SyncData\Source\ProjectsShared\Python\CallPython\CallPython\Program.cs:line 12
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

The team went to lengths to ensure that the error you get at runtime looks almost exactly like the error you would have received at compile time had you attempted to call a static method that did not exist. Everything possible is done to make this experience easy for your to understand and handle, including giving you an exception that you can catch with your own custom code.

It goes without saying that everyone on the Visual Studio Languages team at Microsoft understands the implications of introducing the dynamic keyword into the C# language. We understand completely the advantages of statically linked code, and believe that strict type checking is a powerful tool that helps developers create robust code. In most cases, you should still use statically linked calls in your program. There are, however, scenarios that cannot be efficiently supported from the C# language without the introduction of dynamic programming. One is calling a dynamic language such as Python, and another is calling into a some COM objects, such as those used in those used by Microsoft Office. In order to make it easy for you to use dynamic languages and to perform COM Interop, the dynamic keyword was introduced into the C# 4.0 language, and the DLR became a built-in extension of .NET 4.0. Use these tools judiciously, and they will be your friend. Abuse them, and you are on your own.

Summary

In this article you learned how to call a simple Python script from a C# application. You saw how to set up the References section for your program so that it contains the assemblies used when calling an IronPython script. You then saw how to write a simple Python script, and how to call it from a C# program.

The dynamic technology on display in this example makes use of a body of code called the DLR, or Dynamic Language Runtime. The dynamic keyword that is part of C# 4.0 provides supports when you call dynamic languages or COM objects, and particularly when you use C# 4.0 to call into Microsoft Office applications. Coverage of calling into Office is separate subject, and will be covered in another post. A third topic deserving of coverage is using the dynamic keyword in scenarios where you previously used reflection.

kick it on DotNetKicks.com