33
$\begingroup$

I'd like to call some IronPython code from Mathematica using .NET/Link.

The problem is that the code lives in .py files, yet from Mathematica I can only load .NET assemblies which are DLLs.

So it looks like I will have to first load the IronPython .NET assembly, instantiate an IronPython language hosting environment, load the .py file into that and only then I'll be able to call the Python code.

Has anybody done such a thing?

$\endgroup$
0

3 Answers 3

22
$\begingroup$

Being the fan of Mathematica<->CLR interop that I am, your question has inspired me to try to get IronPython fully working with Mathematica for the last couple of days.

I haven't yet had total luck. Part of my problem is that I don't have a Windows Mathematica license, so I can't fully double-check my work. While I'm trying to hunt down Mathematica for Windows, here's my work so far. There's a good chance it'll work fine for you.

Note: If it fails on one of the LoadNETType[] lines, let me know. I had to recompile all of the .NET/Link binaries from source to link them against .Net 4.0. The binaries with Mathematica 8, at least on Mac, are linked against .NET 2.0 with an exuberant message that it works just fine with .NET 3.0 since they only added a few namespaces. Unfortunately, that's not much consolation for those of us IronPython users who need .NET 3.5 or newer.

(If it turns out that a version update is necessary on Windows as it was for me and you need instructions for the "rebuild from source", let me know in comments, and I can write up my build steps.)

(* .NET/Link ceremony *)
Needs["NETLink`"]
InstallNET[]

(* This is one of the steps that doesn't really work for me,
   but I think it'll work on Windows. *)

ShowNETConsole[]

(* IronPython-specific imports *)

LoadNETAssembly["/PATH/TO/IronPython.dll"]
LoadNETAssembly["/PATH/TO/Microsoft.Scripting.dll"]
LoadNETType["IronPython.Hosting.Python", StaticsVisible -> True]
LoadNETType["Microsoft.Scripting.SourceCodeKind"]

(* Transliteration of the first IronPython hosting example from
   http://www.voidspace.org.uk/ironpython/hosting_api.shtml *)

engine = IronPython`Hosting`Python`CreateEngine[]
st = SourceCodeKind`Statements
source = "print 'Hello World'";
script = engine@CreateScriptSourceFromString[source, st]
scope = engine@CreateScope[]
script@Execute[scope]

Note: You'll have to change /PATH/TO/ above (2 occurrences) to be the correct path to the IronPython binaries on your system. An alternative would be to install them in the GAC.

As I hinted in the code comments above, there's a pretty good crash-course on the IronPython hosting API on Michael Foord's blog.

$\endgroup$
1
  • $\begingroup$ There is a way to avoid rebuilding the NETLink component for .NET 4.0 - you can just edit the InstallableNET(32).exe.config file and replace <supportedRuntime version="v2.0.50727" /> with <supportedRuntime version="v4.0" />, as suggested by a Wolfram engineer in forums.wolfram.com/mathgroup/archive/2011/Aug/msg00115.html $\endgroup$
    – Meh
    Feb 18, 2012 at 16:03
27
$\begingroup$

IronPython requires .NET 4.0 to run. As of V8, Mathematica launches .NET 2.x by default. See this question for details about how to use .NET 4.0. Having done that, we need to load the IronPython assembly into the .NET framework:

Needs["NETLink`"]
InstallNET[];

$pythonDll = "C:\\Program Files (x86)\\IronPython 2.7.1\\IronPython.dll";
LoadNETAssembly[$pythonDll];
LoadNETType["IronPython.Hosting.Python"];

Adjust the file path appropriately.

We are going to use the IronPython Hosting API which is documented in the help file that comes with the IronPython distribution. First, we will create a script engine and a Python scope to hold any variables we define:

$pythonEngine = Python`CreateEngine[];
$pythonScope = $pythonEngine@CreateScope[];

Python expressions can be evaluated thus:

$pythonEngine @ CreateScriptSourceFromString["1 + 1"] @ Execute[$pythonScope]

2

$pythonEngine @ CreateScriptSourceFromString["range(10)"] @ Execute[$pythonScope]

NETObject$46131891404801

Hmmm, it is not so convenient to have lists returned as opaque .NET objects. Let's introduce a bit of framework to interconvert Mathematica and Python values. We will package up the evaluation code into the function pythonEvaluate:

pythonEvaluate[expr_String] :=
  pythonBlock @ $pythonEngine @
    CreateScriptSourceFromString[expr] @ Execute[$pythonScope]

It uses pythonBlock which will take care to convert any values we get back from Python -- and also clean up any leftover .NET objects that are created in intermediate calculations:

SetAttributes[pythonBlock, HoldAll]
pythonBlock[body_] := NETBlock[fromPython[body]]

fromPython will convert Python values to Mathematica ones. Basic types like integers, reals and strings are automatically converted by NETLink. Here we show an explicit conversion for Python lists. Other objects are still returned as raw .NET objects.

$pythonListType = LoadNETType["IronPython.Runtime.List"];

fromPython[v_?NETObjectQ] /; InstanceOf[v, $pythonListType] :=
  Table[fromPython @ v @# &@ i, {i, 0, v @ UUlenUU[] - 1}]

fromPython[v_?NETObjectQ] := KeepNETObject @ v

fromPython[v_] := v

Now, we can evaluate some Python expressions that return lists.

pythonEvaluate["range(10)"] 

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

pythonEvaluate["[x for x in xrange(1, 30) if x % 3 == 0 or x % 5 == 0]"]

{3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27}

It would be nice to be able to invoke Python functions without having to build up the call expression as a string. pythonFunction makes this possible:

pythonFunction[f_String][args___] :=
  pythonBlock @ Module[{pf, netArgs}
  , pf = $pythonScope @ GetVariable @ f
  ; netArgs = MakeNETObject[toPython /@ {args}, "System.Object[]"]
  ; $pythonEngine @ Operations @ Invoke[pf, netArgs]
  ]

It relies upon toPython to convert Mathematica values to Python values. Again, the basic types are converted automatically and here we provide an explicit conversion for lists:

toPython[v_List] :=
  Module[{pList = NETNew[$pythonListType]}
  , Scan[pList @ append @ toPython @ # &, v]
  ; pList
  ]

toPython[v_] := v

Now we can define and call Python functions.

pythonEvaluate[
  "def silly(a,b,c): return '%s/%s/%s' % (a/5, b[::-1].upper(), sum(c))" ]

silly = pythonFunction["silly"];

silly[5, "owt", {0.75, 2.25}]

"1/TWO/3.0"

As a final piece of convenience, we can create input cells that contain Python expressions like this:

pythonCell[] :=
  ( CellPrint[
      TextCell[
        ""
      , "Program"
      , Evaluatable -> True
      , CellEvaluationFunction -> (pythonEvaluate[#]&)
      , CellFrameLabels -> {{None, "IronPython"}, {None, None}}
      ]
    ]
  ; SelectionMove[EvaluationNotebook[], Previous, CellContents]
  )

The resulting cell is evaluatable using SHIFT ENTER.

python cell

$\endgroup$
1
  • $\begingroup$ Very nice! I especially love pythonCell[]. $\endgroup$
    – sblom
    Jul 19, 2012 at 5:01
2
$\begingroup$

Not sure if this will help you but there is already some stuff that gets bundled with Mathematica, which on a Mac can be found here:

/Applications/Mathematica.app/SystemFiles/Links/Python

There is also this

http://code.google.com/p/pythonika/

which has been around for awhile.

$\endgroup$
1
  • 2
    $\begingroup$ Both are for standard Python. The one shipping with Mathematica was not updated since 2007 and it's 4 Python versions behind. Pythonika on the other hand works quite nicely, however I really need IronPython, since my Python scripts use some .NET components. $\endgroup$
    – Meh
    Feb 17, 2012 at 0:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.