Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IronPython integration in C#: a specific problem/question

Tags:

c#

ironpython

I'm working on providing an extensibility mechanism for my C# mapmaking application through IronPython. Everything works fine, but I have a specific requirement which I'm having trouble implementing: I want the user to be able to specify two things:

  1. A file name of the Python script to be loaded
  2. A one-liner string containing Python script which would typically be a call of a function from that Python file (example getTextLabel(element))

These two settings must be separate, but I don't know if it is possible to do this using PythonScript and related classes.

I'm a newbie in Python, perhaps there is another way to achieve this? For performance reasons I want to avoid loading and compiling the Python script file several times (since there could be several above mentioned different "function call" settings and I want to reuse the CompiledCode instance for the file if possible).

UPDATE: @digEmAll gave the correct answer to my question, so I'm accepting it as a valid answer. But if you are concerned with performance, you should also check out my own answer.

like image 935
Igor Brejc Avatar asked Jan 20 '23 06:01

Igor Brejc


2 Answers

You can do something like this:

string importScript = "import sys" + Environment.NewLine +
                      "sys.path.append( r\"{0}\" )" + Environment.NewLine +
                      "from {1} import *";

// python script to load
string fullPath = @"c:\path\mymodule.py";

var engine = Python.CreateEngine();
ScriptScope scope = engine.CreateScope();

// import the module
string scriptStr = string.Format(importScript,
                                 Path.GetDirectoryName(fullPath),
                                 Path.GetFileNameWithoutExtension(fullPath));
var importSrc = engine.CreateScriptSourceFromString(scriptStr,Microsoft.Scripting.SourceCodeKind.File);
importSrc.Execute(scope);

// now you ca execute one-line expressions on the scope e.g.
string expr = "functionOfMyModule()";
var result = engine.Execute(expr, scope);

As long as you keep the scope where the module is loaded, you can call functions of the module without reloading it.

like image 177
digEmAll Avatar answered Jan 29 '23 10:01

digEmAll


I've done some testing of @digEmAll's code. First I must say it runs correctly and does what I asked in the question. But I was concerned with the fact that you have to call

string expr = "functionOfMyModule()";
var result = engine.Execute(expr, scope);

every time you want to evaluate a user-defined expression. My concern was that the code is not pre-compiled and has to be reparsed on each execution, which could have a serious effect on the performance of my application (these kinds of expressions could be called hundreds of thousands if not millions of times, so every millisecond counts).

I tried a different solution: simply pasting the user-defined expression at the end of the Python module (I'm not saying this works in all situations!):

def simpleFunc(x):
    return x + 2;

# this is where the pasting occurs:
simpleFunc(x)

What I did then is to compile this code:

 ScriptSource source = engine.CreateScriptSourceFromString(myCode);
 CompiledCode compiledCode = source.Compile();

... create a scope and run it:

 ScriptScope scope = engine.CreateScope();
 scope.SetVariable ("x", 10);
 int result = compiledCode.Execute<int>(scope);

Now I executed both solutions (digEmAll's and my own) on the same piece of code and the same expression 10,000 times and here are the results:

  • engine.Execute(expr, scope): 0.29 ms / run
  • compiledCode.Execute(scope): 0.01 ms / run

So I guess I'll try to use my own solution, unless the code pasting proves to be a problem.

like image 25
Igor Brejc Avatar answered Jan 29 '23 12:01

Igor Brejc