在C#环境中动态调用IronPython脚本

在C#环境中动态调用IronPython脚本(一)

本文讲述用C#调用Ironpython运行环境,解析并运行动态pyhton脚本。这种情况应用在那些需要滞后规定行为的场合,例如,动态计算项(计算引擎),用户可以自定义计算内容、计算公式等。

        本文的代码适用于IronPython 2.7(需要下载及安装)及C#4.0,由于IronPython早期版本提供的Hosting API不同,对于网上搜索得到的代码,如果是早期版本代码(多数如此),并不能编译通过,所以本文强调代码的版本问题。

        本文代码需要引用两个命名空间IronPython和Microsoft.Scripting (文件位置分别为IronPython 2.7的安装目录下的IronPython.dll和Microsoft.Scripting.dll)。 

一、最简单的例子

        先看一个最简单的例子,C#环境与Python环境没有数据联系。       
[csharp] view plain copy
  1. public static void test1()  
  2. {  
  3.       var engine = IronPython.Hosting.Python.CreateEngine();  
  4.       engine.CreateScriptSourceFromString("print 'hello world'").Execute();  
  5.       Console.ReadLine();  
  6. }  

        如果IronPython环境建立好,运行test1(),就会得到正确的结果。如果只想运行一段脚本,就是这般简单。

二、C#环境调用Python环境函数

        以上的例子没有实用价值,再看第二个例子,这个例子演示了如何从C#环境调用Python环境中的函数以及类中方法。

[csharp] view plain copy
  1. public static void test2()  
  2.        {  
  3.            var engine = Python.CreateEngine();  
  4.            var scope = engine.CreateScope();  
  5.            var source = engine.CreateScriptSourceFromString(  
  6.                "def adder(arg1, arg2):\n" +  
  7.                "   return arg1 + arg2\n" +  
  8.                "\n" +  
  9.                "def fun(arg1):\n" +  
  10.                "   tel = {'jack': 4098, 'sape': 4139}\n" +  
  11.                "   for k, v in arg1.iteritems():\n"+  
  12.                "      tel[k]=v\n"+  
  13.                "   return tel\n" +  
  14.                "\n" +  
  15.                "class MyClass(object):\n" +  
  16.                "   def __init__(self, value):\n" +  
  17.                "       self.value = value\n");  
  18.            source.Execute(scope);  
  19.   
  20.            var adder = scope.GetVariable<Func<objectobjectobject>>("adder");  
  21.            Console.WriteLine(adder(2, 2));  
  22.   
  23.            var fun = scope.GetVariable<Func<objectobject>>("fun");  
  24.            IronPython.Runtime.PythonDictionary inputDict = new IronPython.Runtime.PythonDictionary();  
  25.            inputDict["abc"] = "abc";  
  26.            inputDict["def"] = 456;  
  27.            object res = fun(inputDict);  
  28.            IronPython.Runtime.PythonDictionary outputDict = res as IronPython.Runtime.PythonDictionary;  
  29.            foreach (var k in outputDict.Keys)  
  30.            {  
  31.                Console.WriteLine("key:"+ k.ToString()+" val:  " + outputDict[k].ToString());  
  32.            }  
  33.   
  34.            var myClass = scope.GetVariable<Func<objectobject>>("MyClass");  
  35.            var myInstance = myClass("hello");  
  36.   
  37.            Console.WriteLine(engine.Operations.GetMember(myInstance, "value"));  
  38.        }  

上面代码中,python中有两个函数和一个类,第一个函数的参数是简单数据类型,第二个是复杂数据类型(关于两个环境下复杂数据类型的对应,下面将论述)。无论是类还是函数,C#的调用方法都是通过ScriptScope.GetVariable,它的函数定义如下:

T GetVariable<T>(string name);

ScriptScope还有一个更“安全”的方法

boolTryGetVariable<T>(string name, out T value);

可以完成相似的操作。

这个例子,可以扩展C#的应用,例如,python有丰富的数学计算库,而C#在这方面较欠缺,这时,就可以采用上面的方式,计算部分采用现成的python库,而主控程序采用C#编制。

三、在Python环境中调用C#环境函数

现在看第三个例子,如果Python运行逻辑复杂,需要在运行过程中调用C#函数怎么办?


[csharp] view plain copy
  1. public static void test3()  
  2.         {  
  3.             var engine = Python.CreateEngine();  
  4.             var scope = engine.CreateScope();  
  5.   
  6.             scope.SetVariable("my_object_model"new CSharpClass ());  
  7.             string pythonscript =  
  8.                 "def fun(arg1):\n" +  
  9.                 "   result = arg1+1\n" +  
  10.                 "   return result\n" +  
  11.                 "adder = fun(5) + my_object_model.Foo(2)\n" ;  
  12.              engine.CreateScriptSourceFromString(pythonscript).Execute(scope);  
  13.              Console.WriteLine(scope.GetVariable("adder"));  
  14.         }  
  15. Class CSharpClass  
  16. {  
  17.         public int Foo(int arg)  
  18.         {  
  19.            return  arg +1;  
  20.         }  
  21. }  

这个例子中,创建CShparpClass类,并将其作为“变量”传到Python环境中,在Python中就可以调用了。注意到C#中的类名可以和Python中不一样。


四、在Python环境中动态调用C#库

        在这种情况下,Python脚本和C#库都是“滞后”于主应用才编写出来的,可以满足用户现场定制行为(采用Python脚本),并且可以给Python脚本传入现场定制的参数。

首先,建立一个C#库,代码如下:

 
[csharp] view plain copy
  1. namespace LibforPython  
  2. {  
  3.     public class PythonLib  
  4.     {  
  5.         public int Test(int x, string op)  
  6.         {  
  7.             switch (op.ToUpper())  
  8.             {  
  9.                 case "INC":  
  10.                     return x + 1;  
  11.                 case "DEC":  
  12.                     return x - 1;  
  13.             }  
  14.             return x + 1;  
  15.         }  
  16.     }  
  17. }  

编译成LibforPython.dll后拷贝到主运行程序的运行目录(二者同目录)。调用代码如下:
[csharp] view plain copy
  1. public static void test4()  
  2.   {  
  3.       var engine = Python.CreateEngine();  
  4.       var scope = engine.CreateScope();  
  5.       engine.Runtime.LoadAssembly(Assembly.LoadFrom("LibforPython.dll"));              string pythonscript =  
  6.          "from LibforPython import PythonLib\n" +  
  7.          "o = PythonLib()\n" +  
  8.          "res = o.Test(6,'add')\n";  
  9.       engine.CreateScriptSourceFromString(pythonscript).Execute(scope);  
  10.       Console.WriteLine(scope.GetVariable("res"));  
  11.   }  

运行以上程序即可。这个例子中,LibforPython.dll是在运行时才引入Python环境中的。对于预先已知的Python可能用到的接口,才用例三的办法更好些,对于预先无法预先定义或“遗忘”的接口,采用本例比较适合。


五、总结

    将Python环境“寄宿”于C#环境,进而动态执行用户自定义的脚本,是应用可配置性、灵活性的一种体现(其他动态语言也可以这么做,以Ironpython比较简单)。这一过程包括以下三步:

           var engine = Python.CreateEngine();

           var scope = engine.CreateScope();

           var source = engine.CreateScriptSourceFromString(“…”);

           source.Execute(scope);

Python环境与宿主环境的交互(参数传入、传出),则通过ScriptScop的GetVariable和SetVariable进行。

        下一个问题就是Python复杂数据类型与C#复杂数据类型的对应以及出错处理等,将在下一篇介绍。这里只把复杂数据类型的对应列出来。

C#                                                           Python      

IronPython.Runtime.List      ――――  List

IronPython.Runtime.SetCollection ―――  Set

IronPython.Runtime.PythonDictionary ―― Dictionary


在C#环境中动态调用IronPython脚本(二)

一、Python数据类型与C#数据类型的对应

      Python中数据类型中的简单类型,例如int,float,string可以对应到C#环境中的int32,double,string,这些对应比较直观,Python中的复杂数据类型,例如List,Set等是C#环境中没有的,好在IronPython提供了这些数据类型的C#接口,使我们可以在C#环境中使用它们。如下表所示。

C#                                                                     Python      

IronPython.Runtime.SetCollection   ―――  Set

IronPython.Runtime.List                    ――――  List

IronPython.Runtime.PythonDictionary ―― Dictionary

(本文所列出的数据类型足够使用,作为研究,可以列出其他数据类型的对应,但实际编程无此必要。)

下图是SetCollection、List和PythonDictionary的继承关系图。

在C#环境中动态调用IronPython脚本_第1张图片

        笔者以为,这些接口类的作用是与Python“通信”,能够从这些封装体中取出或存入数据即可,这些数据的后处理或前处理宜采用C#环境中的“原生类”比较合适,而不要采用这些封装类中的一些特别的方法。因此这里列出这些封装类的一些基本方法(主要是存取数据方面的)。

[csharp] view plain copy
  1. SetCollection主要方法  
  2. SetCollection   copy ()  
  3. void    clear ()  
  4. IEnumerator< object >     GetEnumerator ()  
  5. int     Count  
  6.   
  7. IronPython.Runtime.List主要方法  
  8. int     index (object item)  
  9. int     index (object item, int start)  
  10. int     index (object item, int start, int stop)  
  11. void    insert (int index, object value)  
  12. void    Insert (int index, object value)  
  13. object  pop ()  
  14. object  pop (int index)  
  15. void    remove (object value)  
  16. void    reverse ()  
  17. void    RemoveAt (int index)  
  18. bool    Contains (object value)  
  19. void    Clear ()  
  20. int     IndexOf (object value)  
  21. int     Add (object value)  
  22. void    CopyTo (Array array, int index)  
  23. void    CopyTo (object[] array, int arrayIndex)  
  24. bool    Remove (object item)  
  25. int     Count  
  26.   
  27. PythonDictionary主要方法  
  28. void    Add (object key, object value)  
  29. bool    ContainsKey (object key)  
  30. bool    Remove (object key)  
  31. bool    TryGetValue (object key, out object value)  
  32. void    Add (KeyValuePair< objectobject > item)  
  33. void    Clear ()  
  34. bool    Contains (KeyValuePair< objectobject > item)  
  35. void    CopyTo (KeyValuePair< objectobject >[] array, int arrayIndex)  
  36. int     Count  
这些方法与C#固有的集合类很类似,比较好用。

二、脚本错误处理

        动态脚本的运行,由于有用户参于的成份,因此出错的可能性很大,脚本的解析和运行,应该包含在一个大的Try…Catch中,应用程序不应该因为脚本的错误而中断,能够给出一个友好的、有意义的出错信息,是这类程序必须考虑的问题。

1.      如果脚本编写错误(语法错误),在执行对脚本执行Execute时,产生SyntaxErrorException。

2.      脚本中,如果没有找到类或方法,产生UnboundNameException,类中未定义方法,产生 MissingMemberException,方法传入参数个数错误,产生ArgumentTypeException, 传入参数类型不对,产生TypeErrorException

3.      如果脚本运行正确,在GetVariable时,变量名字写错了,会产生MissingMemberException。

4.      脚本中没有找到方法或类UnboundNameException, 类中未定义方法MissingMemberException。

以上只列出了脚本语法错误和调用错误,未包含运行错误(例如被零除),更详细的异常信息,请查阅Ironpython的帮助文档。


IronPython引用C#编写的Dll的几个方式



1.当前运行目录
import clr
import sys
import System
sys.path.append(System.AppDomain.CurrentDomain.BaseDirectory)
clr.AddReferenceToFile("abc.dll")

2.绝对路径
import clr
import sys
sys.path.append(r'c:\dll')
clr.AddReferenceToFile("abc.dll")

3.指定的程序集
import clr
import sys
import System
aso = System.Reflection.Assembly.LoadFrom('abc.dll') //这里可以是绝对路径的dll,默认是当前运行路径
clr.AddReference(aso)

另外

sys.path.append 导入目录的时候,别出现dll重复的情况

你可能感兴趣的:(在C#环境中动态调用IronPython脚本)