Posted on March 7, 2008 22:44 by jpager

I don't know about you, but I am a fan of python. It has the advantages of flexibility that come from weakly typed languages, but maintains many of the functions of typical object-oriented languages. Though it feels like a scripting language, you can even write fully-featured standalone applications if you feel so inclined. So, naturally, I thought it would be fun to explore the possibility of using Python with .NET.

The de-facto standard .NET implementation of Python is IronPython. IronPython is a Python implementation that is written in .NET, and has use of all of the .NET CLR functionality. This allows us a couple of possibilities. We can use it as a python interpreter that has access to .NET functionality (for example, Windows Forms). However, as Scott has pointed out, if you're looking for a Python interpreter, you might as well use the native one, and if you're writing a .NET application, you might as well use C#. The other way we can use IronPython is purely as a scripting language that can be embedded in a C# (or VB, if you swing that way) application. (Particular ways to use scripting in .NET, while perhaps the most practical aspect of this discussion, will be reserved for some other time. My focus will be nothing more than using IronPython in a C# application.) So, without further ado...

Using IronPython in C#

At the center of the IronPython module is the PythonEngine object. So, the first thing we need to do is import our IronPython dll and create our PythonEngine object. Let's start by importing the IronPython.Hosting namespace:

using IronPython.Hosting;  

We create our object like so:

     using (PythonEngine engine = new PythonEngine()) {
     }  

Now that we have our PythonEngine, we can explore some of its basic functions.

The most basic function of PythonEngine is the Evaluate() method. Evaluate simple executes a single Python expression (note: by this I really mean expression, not statement. If you try an assignment, it will complain about bad python syntax.) and returns the result as an object (there is also a strongly-typed version, EvaluateAs, which I will use in my examples).

Let's try approximating PI using a python expression:

     double pi = engine.EvaluateAs<double>("4/1.0 - 4/3.0 + 4/5.0 - 4/7.0 + 4/9.0 "
          + "- 4/11.0 + 4/13.0 - 4/15.0 + 4/17.0 - 4/19.0 + 4/21.0 - 4/23.0 + 4/25.0 "
          + "- 4/27.0 + 4/29.0 - 4/31.0 + 4/33.0 - 4/35.0 + 4/37.0 - 4/39.0 + 4/41.0 "
          + "- 4/43.0 + 4/45.0 - 4/47.0 + 4/49.0 - 4/51.0");   

When we check the value of our pi variable, we get the value 3.1031453128860127, just as we expect.

Obviously, our value of pi is a little off, so we probably want to use the value of pi in python's math module. So, let's change our expression to this:

     double pi2 = engine.EvaluateAs<double>("math.pi"); 

If you try this, you'll get a runtime error: PythonNameErrorException: name 'math' not defined (and yes, the Exception class is "PythonNameErrorException"). This brings us to our next feature of the PythonEngine: Import. To get this to work, we need to import the math module, like so:

     engine.Import("math");
     double pi2 = engine.EvaluateAs<double>("math.pi"); 

Now, as we would expect, our pi2 variable now contains the value 3.1415926535897931. If you want to use your native python implementation's modules, you need to add those modules' path to your PythonEngine before it could find them:

     engine.AddToPath("C:\\Python25\\Lib");  

Let's move on to something a little more complicated: CreateMethod<TDelegate>(). CreateMethod is a strongly-typed function that returns a delegate. So, obviously, we need to define our delegate, with a contract that the function must adhere to:

     public delegate string DoSomethingDelegate (string param); 

CreateMethod has four overloads, but the one we'll deal with is the one that takes a python statement as the first parameter and a list of type names (the function signature) as the second:

     List<string> _params = new List<string>(new string[]{"param"});  
     DoSomethingDelegate function = engine.CreateMethod<DoSomethingDelegate>(@"
return 'Did you say \'' + param + '?\''  
          ", _params); 
     string rtn = function("TEST"); 

The last feature we'll look at is PythonEngine's Execute method. Execute is pretty self-explanatory--it simply executes Python code. The difference between Execute and the previous functions we've discussed is that Execute does not return anything--it only has side effects. It is important to understand that the PythonEngine works similarly to a native Python shell: any names that are defined while executing code are stored in the engine for future use. That is, if you define a name when you call Execute(), that name will be available to every subsequent Execute() call. Here is a simple example:

      engine.Execute(@"
def Foo():
     return 'Bar'
          ");
     string foo = engine.EvaluateAs<string>("Foo()"); 

In our execute() call, we defined a function called Foo(), and then referenced that same function when we called EvaluateAs().

Next, we'll actually call one of our Python functions from C# code. All of the global names in our PythonEngine are publicly accessible through the PythonEngine's Globals property. Any name that is a function is represented by the C# type IronPython.Runtime.Calls.PythonFunction, which has a method called, aptly enough, Call():

      engine.AddToPath("C:\\Python25\\Lib");
     engine.Import("os");
     engine.Execute(@" 
def RunCommand(command):
     p = os.popen(command)
     output = p.read()
     return output
          ");
     string ipconfig = (string)((IronPython.Runtime.Calls.PythonFunction)engine.Globals["RunCommand"])
          .Call(new object[] { "ipconfig" }); 

This does nothing more than create a process with the command sent through the parameter (which in this case is ipconfig) and read the output stream of the process. Because it uses the native Python os module, I added the path to that module to the PythonEngine's path. After this code executes, the ipconfig variable contains the output of the Windows ipconfig utility.

Now, let's put this to use and create something with it. We'll build a simple web form containing a panel, a text box, and a button. When the button is clicked, the command in the text box will be executed, and the output displayed in the panel (Disclaimer: Whatever you do, do not deploy this page to a network facing web site, for reasons that should be obvious to you).

Here's a snippet of our .aspx page:

     <asp:Panel ID="pnlOutput" runat="server" />
     <asp:TextBox ID="txtCommand" runat="server" Width="600" />
     <asp:Button ID="btSubmit" Text="Execute" runat= server  /> 

Here's our button's event handler:

     void btSubmit_Click(object sender, EventArgs e)
     {
          using (PythonEngine engine = new PythonEngine())
          {
               engine.AddToPath("C:\\Python25\\Lib");
               engine.Import("os");
               engine.Execute(@"
def Execute(command):
     pipe = os.popen(command)
     return pipe.read()
                    ");
               PythonFunction command = (PythonFunction)engine.Globals["Execute"];
               string res = (string)command.Call(new object[] { txtCommand.Text });
               pnlOutput.Controls.Add(new LiteralControl("<pre>" + res + "</pre>"));
          }
     } 

And there you have it. Try it out and play with it; we've only barely touched the possibilities.



Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Related posts

Comments

March 10. 2008 09:31

Michael Foord ha scritto:
[cite]Great blog entry. Minor point though - Python is *not* weakly typed (it is strongly typed), it is dynamically typed.[/cite]

Thanks for the clarification. The distinction seems to have slipped by me when I wrote it. I guess the point is that it's not strongly typed. ;)
|

jpager

March 10. 2008 09:51

Great blog entry. Minor point though - Python is *not* weakly typed (it is strongly typed), it is dynamically typed.
|

Michael Foord

Add comment


 

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

November 20. 2008 20:14

|