Embedding IronPython – C# Calling Python Script Part 1

Embedding IronPython is quite trivial and is documented here.

You can call functions in the embedded python module by
(1) retrieving the function using Evaluate()
(2) then apply the function using the Call() method

// IronPython 1.x only
using (IronPython.Hosting.PythonEngine engine 
  = new IronPython.Hosting.PythonEngine())
{
  engine.Execute(@"
  def foo(a, b):
    return a+b*2");

  // (1) Retrieve the function 
  IronPython.Runtime.Calls.ICallable foo
   = (IronPython.Runtime.Calls.ICallable) engine.Evaluate("foo");

  // (2) Apply function 
  object result = foo.Call(3, 25);
}

In IronPython 2.x, ICallable was removed in favour of dynamic sites.

// IronPython 2.x
using (IronPython.Hosting.PythonEngine engine 
  = new IronPython.Hosting.PythonEngine())
{
  engine.Execute(@"
  def foo(a, b):
    return a+b*2");

  // (1) Retrieve the function 
  dynamic foo
   = engine.Evaluate("foo");

  // (2) Check it is callable
  if (IronPython.Runtime.Operations.PythonOps.IsCallable(DefaultContext.Default, function))
  {
    // (3) Apply function 
    object result = foo(3, 25);
  }
}

The ICallable.Call method can only be called varargs-style, and can’t be called using keyword arguments.
(This is probably a bug.)

Call(params object[] args)

To use keyword arguments, you’ll need to cast the function into PythonFunction.

using (IronPython.Hosting.PythonEngine engine 
  = new IronPython.Hosting.PythonEngine())
{
  engine.Execute(@"
  def foo(*args, **kwargs):
    return 'args=%s kwargs=%s' % (args, kwargs)");

  // (1) Retrieve the function. It might be a method or a function
  dynamic foo
   = engine.Evaluate("foo");

  // (2) Apply function/method
  object results = IronPython.Runtime.Operations.PythonCalls.CallWithKeywordArgs(
     DefaultContext.Default,
     function,
     new object[] { "arg1", "arg2", "arg3" },
     new Dictionary<object, object>()
     {
        {"keywordarg1", 101},
        {"keywordarg2", 202},
        {"keywordarg3", 303}
     });;
}

Note:
Unfortunately, Python classes aren’t CLR classes, and couldn’t be accessed directly or subclassed by C# by referencing a built assembly. You’ll have to go through the hosting interfaces to get an instance of the class.

3 Comments

  1. Simple and informative, thank you!

  2. May I ask for your advice/help?

    I have a C# app which I need to load user defined formula from a database and executed them in order to get results. It would look something like this

    foreach customer (about 100 of them)
    {
    Load about 20 formulas and stick them in a dictionary keyed by a GUID
    foreach employee of the customer (about 10,000 of them)
    {
    employee.X = the result of formula 1
    employee.Y = the result of formula 2
    etc
    }
    }

    Someone has suggested I embed IronPython, but being a complete newbie I have no idea what I am doing. In addition I need to

    1: Ensure the formula returns a decimal value by compiling it (not running it) so I can validate the user’s input.
    2: Access values from the DB via some kind of custom method, such as GetValue(“Order total”)

    Could you help me out please?

    Thanks

  3. Unfortunately, Python is dynamically typed, and you can only check at runtime that each result returned in a decimal value. To achieve step #1, you will have to perform a runtime cast, and trap for errors.

    As for #2, you need to define a C# method called GetValue and follow the steps here http://www.voidspace.org.uk/ironpython/hosting_api.shtml#preloading-assemblies

Comments are closed.