instantiate python class into C# using the class name as string - c#

This SO question provides code to create an instance of a python class in C#.
The following code forces to know the python function name in advance. However I need to specify the class name and the function name to be executed by strings.
ScriptEngine engine = Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromFile("Calculator.py");
ScriptScope scope = engine.CreateScope();
source.Execute(scope);
dynamic class_object = scope.GetVariable("Calculator");
dynamic class_instance = class_object();
int result = class_instance.add(4, 5); // I need to call the function by a string

Easiest way to do that is to install nuget package called Dynamitey. It was designed specifically to call dynamic methods (and doing other useful things) on dynamic objects. After you install it, just do:
static void Main(string[] args)
{
ScriptEngine engine = Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromFile("Calculator.py");
ScriptScope scope = engine.CreateScope();
source.Execute(scope);
dynamic class_object = scope.GetVariable("Calculator");
dynamic class_instance = class_object();
int result = Dynamic.InvokeMember(class_instance, "add", 4, 5);
}
If you want to know what it does under the hood - it uses the same code which is used by C# compiler for dynamic invocations. This is a long story but if you want to read about this, you can do it here for example.

You're looking for the Invoke and InvokeMember IronPython methods:
ScriptEngine engine = Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromFile("Calculator.py");
ScriptScope scope = engine.CreateScope();
source.Execute(scope);
object class_object = scope.GetVariable("Calculator");
object class_instance = engine.Operations.Invoke(class_object);
object[] args = new object[2];
args[0] = 4;
args[1] = 5;
int result = (int)engine.Operations.InvokeMember(class_instance, "add", args); // Method called by string
// "args" is optional for methods which don't require arguments.
I also changed the dynamic type to object, since you won't need it anymore for this code sample, but you are free to keep it, should you need to call some fixed-name methods.

Related

Why do I get Microsoft.Scripting.SyntaxErrorException: unexpected token ',' when importing libraries using IronPython

I'm using IronPython v 2.7.8.1 in VS 2017. I've installed Python27, 36 and 37. I've tried switching between the various environments in VS. I've tried adding the search paths to the libraries of these installs. The python code will work if I run it in the interpreter. Trying to run the same code in VS throws: "Microsoft.Scripting.SyntaxErrorException: unexpected token ',' ". If I test a python script that doesn't include imports it will work? Is there a specific way python has to be installed to work IronPython? This is the C# Code:
class CallPython
{
public void PatchParameter(string parameter)
{
var FilePath = (#"C:\Users\Pac\Downloads\MvcAuth\MvcAuth\TwurlPy\TwurlPy\SendDirectMsg.py");
var engine = Python.CreateEngine(); // Extract Python language engine from their grasp
ICollection<string> searchPaths = engine.GetSearchPaths();
searchPaths.Add(#"C:\Users\Pac\AppData\Local\Programs\Python\Python37\Lib");
searchPaths.Add(#"C:\Users\Pac\AppData\Local\Programs\Python\Python37\Lib\site-packages");
engine.SetSearchPaths(searchPaths);
var scope = engine.CreateScope(); // Introduce Python namespace
(scope)
var d = new Dictionary<string, object>
{
{ "text", text},
{ "userID", userID},
};
// Add some sample parameters. Notice that there is no need in
// specifically setting the object type, interpreter will do that part for us
// in the script properly with high probability
scope.SetVariable("params", d); // This will be the name of the
// dictionary in python script, initialized with previously created .NET
// Dictionary
ScriptSource source = engine.CreateScriptSourceFromFile(FilePath);
// Load the script
object result = source.Execute(scope);
parameter = scope.GetVariable<string>("parameter"); // To get the
// finally set variable 'parameter' from the python script
return;
}
}
This is the Python script. If I comment out the import statements it works using IronPython, but of course I need them...
import twitter
import requests
import sys
parameter = "test"
def SendDM(text, userID, access_token, access_secret):
consumer_key = 'YkopsCQjEXccccccccccccccccccZvA9yy'
consumer_secret = 'TQVCoccccccccccccccccccct7y8VfmE'
access_token_key = access_token
access_token_secret = access_secret
api = twitter.Api(
consumer_key=consumer_key,
consumer_secret=consumer_secret,
access_token_key=access_token_key,
access_token_secret=access_token_secret)
send_msg = api.PostDirectMessage(text, user_id=userID)
print (send_msg)
return
SendDM(text, userID, access_token, access_secret)

C# and IronPython integration

I want simply use io.py from C# to write a file and I use the following code:
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
...
System.IO.Directory.SetCurrentDirectory(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
"\IronPython\Lib");
ScriptRuntime py = Python.CreateRuntime();
dynamic io = py.UseFile("io.py");
dynamic f = io.open("tmp.txt", "w");
f.writelines("some text...");
f.close();
but when I run the program the runtime give me a:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException telling that no overload of writelines accept argument '1'
it seems like that method doesn't exists... but into io.py documentation it exists.
P.S. The same is for close method!!!
Any idea?
I can only tell you how to make your code working, however I don't have much experience with IronPython and have no idea why it is done this way (though I try to learn that). First, it seems io module is treated in a special way and there is special (non-dynamic) class for that. When you do io.open what is returned is instance of PythonIOModule._IOBase class. You can do
var f = (PythonIOModule._IOBase) io.open("tmp.txt", "w");
And see for yourself that "writeline" method (which is regular method, not a dynamic one) accepts CodeContext instance as first argument, and second argument is lines. Interesting that this class itself already contains field with that CodeContext, but it is made internal for some reason, and what is even worse - writelines (and other methods) could have been using that CodeContext and not require us to provide external one. Why is it done like this - I have no idea.
So to make your code working, we have to get CodeContext somewhere. One way is do that via reflection:
var context = (CodeContext) f.GetType().GetField("context", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(f);
Another way is to craft it yourself:
var languageContext = HostingHelpers.GetLanguageContext(engine);
var context = new ModuleContext(io._io, new PythonContext(languageContext.DomainManager, new Dictionary<string, object>())).GlobalContext;
Both methods will work and program will successfully write to a file. Full working sample:
static void Main(string[] args) {
System.IO.Directory.SetCurrentDirectory(#"G:\Python27\Lib");
var engine = Python.CreateEngine();
dynamic io = engine.ImportModule("io");
var f = (PythonIOModule._IOBase) io.open("tmp.txt", "w");
var languageContext = HostingHelpers.GetLanguageContext(engine);
var context = new ModuleContext(io._io, new PythonContext(languageContext.DomainManager, new Dictionary<string, object>())).GlobalContext;
f.writelines(context, "some text....");
f.close(context);
}

Accessing WinForm Controls using C# dynamic code

I have WinForm called Form1 and there are Button1, MemoEdit1 and 2 TextBoxes named TextBox1 and TextBox2. At runtime user should be able write C# code in MemoEdit1 in order to manipulate the TextBox controls. F.e: at runtime user typed into MemoEdit1 simple code like: TextBox2.Text = "Hello" + TextBox1.Text;
So, when I click on Button1, I need to compile and execute the code.
Question may sound so simple as I am a newbie in compiling/executing code during runtime in C#.
Could you pls, help?
Thanks.
Take a look on this snippet
public class Evaluator
{
public void Eval(string Code)
{
Microsoft.CSharp.CSharpCodeProvider Provider = new Microsoft.CSharp.CSharpCodeProvider(); // Create an provider
System.CodeDom.Compiler.ICodeCompiler Compiler = Provider.CreateCompiler(); // Create An Compiler
System.CodeDom.Compiler.CompilerParameters Parameters = new System.CodeDom.Compiler.CompilerParameters(); // Create a parameters of the compiler
Parameters.GenerateInMemory = true; // It should generate the compiled assembly in the memory
System.CodeDom.Compiler.CompilerResults Results = Compiler.CompileAssemblyFromSource(Parameters, Code); //Compile it
///Now you just need to use reflection to call its methods
object SomeClass = Results.CompiledAssembly.CreateInstance("ClassName"); //Name of the class you want to create an instance
var Method = SomeClass.GetType().GetMethod("MethodName"); //Name of the Method you want to call
Method.Invoke(SomeClass, null); // change null for the argument it needs
}
}
if you want to just write code you will have to add the an class and a Method to wrap the user code and then you call it through the Invoke, you will probably have to reference your own assembly into this assembly

IronPython 'execfile' Equivalent in C#

Id like to know how to perform the functionality of the 'execfile' Python command
but from C#. In this example:
var runtime = Python.CreateRuntime();
dynamic scope = Python.UseFile("body.py");
you specify the body file.
In the header of body.py, I specify the header:
execfile('header.py')
I would like to make that execfile from C#, how can I do that?
Note: Import does not work because i'm declaring variables dynamically inside
header.py scope. import would not import that variables because they do
not exist yet.
Thank you very much!
If you want different behaviour from the exec file you can easily create your own and do things like this (share scope) by creating the function in .NET and set this to the scope. This would also allow you to add additional functionality if you wish.
This seems to work:
Setting up the engine:
var py = Python.CreateEngine();
var source = py.CreateScriptSourceFromFile(#"..\..\IronPython\body.py");
var scope = py.CreateScope();
Then I create the function that can be used by scripts and set it on the scope
Action<string> execFileCallback = fileName =>
{
var s2 = py.CreateScriptSourceFromFile(fileName);
s2.Execute(scope);
};
((dynamic)scope).myexecfile = execFileCallback;
source.Execute(scope);
Now my Body.py looks like this:
someVarSetByBody = "This was set by the body"
Console.WriteLine("Body is loading")
myexecfile(r"..\..\IronPython\Header.py")
Console.WriteLine("Body has loaded")
And my Header.py Looks like this:
Console.WriteLine("Header is loading")
Console.WriteLine("Variable set by body: %s" % someVarSetByBody)
Console.WriteLine("Header has loaded")
It is now able to use the same scope in the different scripts so you can share the variables.

Error IronPython+C# 3.5

I'm using the implementation of ironpython 2.6.2 in my application c# 3.5, but I'm getting the following error: "The method or operation is not implemented."
Added references to the DLR and IronPython assemblies (all found in the IronPython install directory, which is “C:\Program Files\IronPython 2.6” on my machine):
IronPython.dll
IronPython.Modules.dll
Microsoft.Scripting.dll
Microsoft.Scripting.Core.dll
follows the code of my application:
ScriptEngine engine = Python.CreateEngine();
//parameter file path
ScriptSource source = engine.CreateScriptSourceFromFile(pathFilePy);
ScriptScope scope = engine.CreateScope();
ObjectOperations op = engine.Operations;
source.Execute(scope); // class object
object classObject = scope.GetVariable("calc"); // get class object
object instance = op.Invoke(classObject); // create instance
object method = op.GetMember(instance, "calc01"); // get method
var result = op.Invoke(method, 10, 20,30); // call method and get result
the code file .py
class calc(object):
def calc01(self,var1,var2,var3):
bla = ((var1+var2+var3)/3)
return bla
The error occurs on this line:
var result = op.Invoke(method, 10, 20,30); // call method and get result
Maybe you should call it with 4 arguments instead of 3 like this:
>>> class calc(object):
... def calc01(self,var1,var2,var3):
... bla = ((var1+var2+var3)/3)
... return bla
...
>>> calc1 = calc()
>>> calc.calc01(calc1,10,20,30)
20
>>>
as was pointed out in the other answer it looks like your method needs 4 parameters and is only being invoked with 3 from the c# code. the question I would ask (not being a python expert) is why your python method has the parameter self at all? I would have thought you need to do 1 of 2 things, both of which will probably solve your problem
1 Redfine your python method not to contain self:
class calc(object):
def calc01(var1,var2,var3):
bla = ((var1+var2+var3)/3)
return bla
2 Invoke the python method using 4 arguments:
...
object classObject = scope.GetVariable("calc"); // get class object
object instance = op.Invoke(classObject); // create instance
object method = op.GetMember(instance, "calc01"); // get method
var result = op.Invoke(method, instance, 10, 20,30); // call method and get result
...

Categories