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.
