代码改变世界

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奠定了基础。