IronPython and LINQ to Objects (I): 语言特性
2010-05-03 14:52 liangshi 阅读(999) 评论(2) 编辑 收藏 举报Visual Studio的开发者Harry Pierson在DevHawk上发布了一组文章,讨论了如何在IronPython中使用LINQ to XML。受他的启发,我也对如何在IronPython中使用LINQ to Objects进行了研究。从本篇开始,我将逐步介绍LINQ在IronPyton中的使用方法。
《LINQ in Action》指出,LINQ由两个互为补充的部分组成:一组处理数据的工具和一组对编程语言的扩展。LINQ为C#引入了很多新特性,它们构成了语言集成查询(Language-INtegrated Query)的基础。本文将探讨IronPython是否提供了相似的特性,使得IronPython也具备接纳LINQ的能力。其中,一些C#的例子来自于《LINQ in Action》。
1. 隐式类型局部变量(Implicitly typed local variables)
var i = 5;
静态强类型语言的发展趋势之一是不断增强编译期的自动代码生成。编译器会生成大量的只有编译器才知道名字的(或者名字非常复杂的)“匿名类型”。为了让程序员可以操纵这些类型的对象,C#语言引入了关键字var,通过类型推演技术自动获得对象的类型。在保证类型安全的前提下,简化了代码,提高了程序的可理解性。类似地,C++复用了关键字auto,用于自动地声明对象的类型。
Python是动态强类型语言,不要求程序员声明变量的类型。如果在运行时发生类型错误,Python运行时会抛出异常。因此,与C#对应的Python代码非常简单。
i = 5
2. 对象和集合的初始化器(Object and collection initializers)
new Point {X = 1, Y = 2};
var processes = new List<ProcessData> {
new ProcessData {Id=123,},
new ProcessData {Id=456,}
} ;
C#的对象初始化器可以在一条语句中为对象指定一个或多个字段的值。C#的集合初始化器可以在一条语句中初始化集合中的所有的对象。这种声明式的初始化代码,使得对象创建变得更加轻松。
IronPython开发团队很好地将Python与CLR集成在一起。对于用C#编写的类型,IronPython可以用如下语句初始化。
Point(X = 1, Y = 2)
而Python内建的list、tuple等集合类型也支持声明式的初始化。
processes = [ProcessData(Id = 123,),
ProcessData(Id=456,)]
由于不需要关键字new,IronPython代码较C#代码更简单。实际上,C#是不需要关键字new的。在C++中,关键字new表示对象的内存从堆上分配;程序员可以重载new提供自定义的内存分配方式。在C#中,对象的内存分配方式取决于它是引用类型还是结构类型,而且C#也不允许程序员控制内存分配。因此,其关键字new可以被视为语法“噪音”。
3. Lambda表达式(Lambda expressions)
address => address.City == "Paris"
在引入lambda表达式之后,C#具备了一定的函数式语言的风格:允许程序员将运算逻辑抽象为对象,在代码中构造、传递和调用。虽然delegate和匿名函数也提供一定的函数式风格的支持,但是lambda表达式将C#的表达能力推向了新的高度。
Python也支持lambda表达式,对应的Python代码如下。
lambda address: address.City == "Paris"
4. 扩展方法(Extension methods)
static void Dump(this object o);
扩展方法以非入侵的方式来扩展已有类型,允许程序员方便地添加领域相关的辅助方法。这一思路与Herb Sutter在C++ Coding Standard中提倡的non-virtual and non-friend function相似。在编译时,C#编译器会自动绑定扩展方法。这样,程序员就可以以obj.Dump()的形式来调用扩展方法。
Python语言不支持扩展方法。但是,利用Python语言在运行期修改对象成员的能力,我们仍旧可以方便地扩展对象。
1: def bind1st(func, obj):
2: def binder(*__args, **__kws):
3: return func(obj, *__args, **__kws)
4: return binder
5:
6: class DataObject(object): pass
7:
8: d = DataObject()
9: d.x = 1
10: d.Add = bind1st(lambda self, y: self.x + y, d)
11: d.Add(2) # result is 3
以上代码展示了一种在运行期扩展对象的可能策略。
- 第1~4行的函数bind1st的输入是函子func和对象obj,输出是一个新函子。该新函子调用func,且func第一个参数被绑定为obj。
- 第6行声明了类DataObject,它没有任何成员。
- 第8行定义了变量d,它的类型是DataObject。此时,d没有任何成员。
- 第9行为变量d增加了成员对象x。
- 第10行为变量d增加了成员函数Add。
- 第11行调用变量d的成员函数Add,其返回值是3。
在Python语言中,有多种方法来扩展对象的成员函数。在本系列文章中,我将展示另一种“扩展方法”的实现,并应用在对LINQ to Objects的调用中。
5. 匿名类型(Anonymous types)
var contact = new {Name = "Bob", Age = 8}
匿名类型允许程序员方便地构造匿名数据对象。这在Python中也是非常容易的任务。
1: def makeobj(**kws):
2: class Object:
3: def __init__(self, kws):
4: self.__dict__.update(kws)
5: return Object(kws)
6:
7: contact = makeobj(Name = "Bob", Age = 0)
8: print contact.Name # output is "Bob"
9: print contact.Age # output is "0"
- 第1~5行的函数makeobj是Python Cookbook提供的Python惯用法。
- 第1行声明了函数makeobj。它利用Python语言特性,将其参数序列转换为字典对象kw。
- 第2~4行定义了函数makeobj的内部类Object。Object的初始化函数__init__用字典对象kws初始化对象自身。字典的键成为其数据成员的名字,字典的值成为其数据成员的值。
- 第5行返回一个用kws初始化的Object对象。
- 第7~8行的代码展示了makeobj的调用方法和所得对象的成员。
可见,利用makeobj可以用简短的代码构造出任意成员的对象。这些对象配合动态语言Duck Typing特性,优雅地简化了代码。
综上所述,IronPython可以模拟出LINQ为C#引入的新特性。这为IronPython平滑地调用LINQ to Objects奠定了基础。