初试IronPython与.NET的集成
在得知IronPython如今已步入1.0 RC1后兴奋不已,赶快下载下来试用。虽说功能性变化不多(加入了试用性Python 2.5的语法支持:D),但稳定性如今已提高了不少,应该说可以用来投入正式使用了。:)
如何用好IronPython
翻看了一下Tutorials,发现和以前的内容差不多,按照Tutorials中的方法尝试了一下在C#中嵌入IronPython 代码,发现还很不错,最起码可行,虽然速度肯定没有用C#直接写出来的快(因为IronPython需要动态编译的缘故)。
好的,现在我们拥有了一项较稳定的可以投入使用的新技术了,但是如果没有市场,那么这个新技术是不能够生存的,所以我左思右想 IronPython在企业级开发中到底占了什么地位(因为我是做企业级开发的:)),有何等优势呢?通过分析现有IronPython的特征,我得到了一个结论,那就是它比较适合做脚本引擎或者简单的客户端。以下是我分析的IronPython的一些特征:
- Python的语法,灵活性相当的高!
- 与.NET Framework的交互实现的非常的好,现在用Python的语法也可以用.NET的类库了。
- 在其他.NET语言中可以通过引用IronPython.dll来执行IronPython文件并且可以实现内存共享(在同一AppDomain中)。
- 纯IronPython程序启动非常慢。
- 由于IronPython的特殊性,它所编译出来的程序集中的成员并非你所想象的,所以不能够由其他程序集直接引用使用。这主要还是因为Python是一种动态类型语言,所以无法在编译时给予合适的静态类型(唯一合适的就是System.Object)。另外为了与CPython保持兼容性,IronPython也是以module为单位进行group的,也就是一个module被编译成一个类,这个module中的所有成员,包括class都被当作成员编译在这个类中。
基于以上一些IronPython现有的特征来看,我觉得它更适合使用.NET 程序集,而非为.NET程序提供功能(虽然也是可行的,也是我希望的),所以我建议将IronPython用作客户端以及脚本引擎一类的用途上,因为这类程序是属于consumer类的,它们使用现成的API,但是它们不为其他人提供新的API。
如何用IronPython为.NET程序提供API
读者可能会说我自相矛盾,刚刚才说过IronPython不适合用来提供API,怎么现在又提起这件事来了呢(唉,人本身就是一种很矛盾的生物)?!其实就像上面我在括号中提及的,利用IronPython的交互能力与语言的灵活性为我的.NET程序提供简化的解决方案是我的期望,虽然速度方面还不尽人意,但优化的空间永远都是存在的,我相信随着时间的推移IronPython会在性能方面做的更出色(从这方面来看我是个乐观主义者:D)。
为了简化主程序与IronPython文件的交互与维护难度,我决定将IronPython文件作为服务(service)来看待,IronPython所提供的每一个API就是一个独立的服务,每次需要使用这个服务的时候我只需向它发送相应的消息(message)即可,而它,会返回给我一个回应(response)如果有必要的话。我觉得这样做比较符合目前的SOA潮流。(如果你还不知道SOA是什么的话,一句话告诉你,它是一种企业级集成手段)
我的做法很简单,只是初步实现了一个对IronPython文件调用的封装,示例如下。
a.Add(1);
a.Add(2);
a.Add(3);
List<int> b = new List<int>();
b.Add(4);
b.Add(5);
b.Add(6);
PyFacade<IList<IList<object>>> py =
new PyFacade<IList<IList<object>>>();
py.PythonFile = "mayorThenTen.py";
py.Arguments.Add("listA", a);
py.Arguments.Add("listB", b);
py.Execute();
foreach (IList<object> tuple in py.Result) {
Console.Write(tuple[0]);
Console.Write(", ");
Console.Write(tuple[1]);
Console.WriteLine();
}
PyFacade的代码如下。
using System.Collections.Generic;
using IronPython.Hosting;
namespace Cavingdeep.Python {
public class PyFacade<T> {
public const string ResultVariableName = "_result_";
private PythonEngine engine = new PythonEngine();
private string pythonFile;
private Result<T> result = new Result<T>();
private IDictionary<string,object> args =
new Dictionary<string,object>();
public string PythonFile {
get {return this.pythonFile;}
set {this.pythonFile = value;}
}
public T Result {
get {return this.result.Value;}
}
public IDictionary<string,object> Arguments {
get {return this.args;}
}
public void Execute() {
engine.Globals[ResultVariableName] = this.result;
foreach (KeyValuePair<string,object> arg in this.args) {
engine.Globals[arg.Key] = arg.Value;
}
engine.ExecuteFile(this.pythonFile);
}
}
}
Result的代码如下。
namespace Cavingdeep.Python {
public class Result<T> {
public T Value;
}
}
mayorThenTen.py的代码如下,这是这个示例中用到的IronPython文件。
return [(x, y) for x in a for y in b if x + y > 5]
if globals().has_key('_result_'):
_result_.Value = mayorThenTen(listA, listB)
这里的这个PyFacade是个比较通用的与IronPython交互的接口类,在真正的应用中建议用强类型模式(强类型设计实践)加以封装以得到type safety等好处。
关于PyFacade是如何与IronPython文件交互一题,简单地说就是通过那个_result_变量,它会在IronPython文件中被赋值,这个值是外界可见的,也就是说IronPython文件的执行与当前程序是在一个AppDomain中的。有关这类知识感兴趣的读者可以去研究一下动态编译、加载与程序域(AppDomain)等知识。