您的位置:首页 > 移动开发 > Objective-C

IronPython and LINQ to Objects (II): LINQ 构建块

2010-05-03 15:48 330 查看
在第一篇文章中,我讨论了如何用IronPython来模拟C#的语言扩展。在这篇文章中,我将进一步讨论如何用IronPython来构造LINQ查询。如果您读过《LINQ in Action》,您会发现我是依据此书来组织本系列文章的。我的第一篇文章对应《LINQ in Action》的第2章“C#和VB的语言增强”,本文对应第3章“LINQ构建块”。

1. IEnumerable<T>

在意图上,LINQ to Objects旨在查询内存中的数据源;在技术上,这些数据源是实现了System.Collections.Generic.IEnumerable<T>接口的对象。C#程序员常常使用.NET Framework提供了泛型容器作为数据源对象,它们位于System.Collections.Generic名空间下并实现了IEnumerable<T>。那么,IronPython程序员经常使用Python标准容器,包括列表(list)、元组(tuple)、集合(set)和字典(dict),实现了IEnumerable<T>吗?在IronPython 2.x的命令行上运行如下语句,就可以知道答案。

>>> import System

>>> ie_object = System.Collections.Generic.IEnumerable[System.Object]

>>> isinstance(list(),ie_object)

True

>>> isinstance(tuple(), ie_object)

True

>>> isinstance(set(), ie_object)

True

>>> isinstance(dict(), ie_object)

False

由运行结果可知,list、tuple和set实现了IEnumerable<Object>,而dict没有实现。实际上,C#也面临相似的问题。在.NET Framework中存在大量没有实现IEnumerable<T>的非泛型容器,程序员也会实现自定义的容器。如何将这些容器方便地配接(adapt)到IEnumerable<T>呢?C#设计者给出的答案是迭代器。

2. 迭代器(Iterator

迭代器是一个用于遍历集合元素的对象。由于这是一种非常有用的设计模式,.NET Framework提供了迭代器接口IEnumerable(以及相应的范型接口IEnumerable<T>),C#语言则提供了关键字yield,以便直接构造实现了IEnumerable的迭代器类型。利用迭代器,C#程序就可以将非泛型容器、自定义容器和序列配接到IEnumerable<T>上。例如,Enumerable为非泛型容器提供了扩展方法Cast,它的一种可能实现如下。

1: public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)

2: {

3: foreach (object elem in source)

4: {

5: yield return (TResult)elem;

6: }

7: }

在该实现中,Cast利用foreach迭代非泛型容器,将其中的元素强制转换为目标类型TResult的对象,然后用yield构造出实现了IEnumerable<T>的迭代器。这段代码简单明了,也很容易应用到其他需要IEnumerable<T>的情景中。

与C#类似,Python提供了关键字yield用于生成迭代器。此外,Python还提供迭代器的另一种形式:生成器(Generator)。利用它们可以将IronPython中的dict和自定义序列配接到IEnumerable<T>。在IronPython 2.x的命令行上运行如下语句,可知yield和生成器所返回的对象都实现了IEnumerable<Object>。

1: >>> import System

2: >>> ie_object = System.Collections.Generic.IEnumerable[System.Object]

3: >>> def seq(num):

4: ... for i in range(num):

5: ... yield i

6: ...

7: >>> isinstance(seq(1), ie_object)

8: True

9: >>> isinstance((i for i in range(1)) # (i for i in range(1)) is a generator,

10: ... # whose result is the same with seq(1).

11: ... , ie_object)

12: True

3. 查询操作符(Query Operator

LINQ to Objects的查询操作符是定义在System.Linq.Enumerable类中的扩展方法。其签名形如:

1: public static int Count<TSource>

2: (this IEnumerable<TSource> source);

3:

4: public static IEnumerable<TResult> Select<TSource, TResult>

5: (this IEnumerable<TSource> source, Func<TSource, TResult> selector);

6:

7: public static IEnumerable<TSource> Where<TSource>

8: (this IEnumerable<TSource> source, Func<TSource, bool> predicate);

由于它们都是泛型函数,IronPython在调用它们时必须指定TSource等类型参数的具体类型。Harry Peirson在他的IronPython and Linq to XML中提供了一组辅助函数来简化IronPython的调用代码。

1: def Count(col):

2: return Enumerable.Count[object](col)

3:

4: def Select(col, fun):

5: return Enumerable.Select[object, object](col, Func[object, object](fun))

6:

7: def Where(col, fun):

8: return Enumerable.Where[object](col, Func[object, bool](fun))

以辅助方法Where(col, fun)为例,它将Enumerable.Where的类型参数TSource指定为object(即System.Object),也就是将该函数的第一个参数具现为IEnumerable<Object>,这样就可以将IronPython中的容器和生成器传递给该参数。然后它把IronPython中的函子fun包装为System.Func的对象,并将该对象传递给Enumerable.Where的第二个参数。这样包装的原因是,Enumerable.Where只接受Func对象,而不接受IronPython定义的函数和lambda表达式。

有了这样一组辅助方法,我们就可在IronPython中调用LINQ to Objects了。例如,以下这条C#语句

int count = Process.GetProcesses()

.Where(process => process.WorkingSet64 > 20*1024*1024)

.Count();

就可以用IronPython实现为

processes = Process.GetProcesses()

processes = Where(processes, lambda p: p.WorkingSet64 > 20*1024*1024)

cnt = Count(processes)

4. 查询表达式(Query Expression

查询表达式是C#编译器提供的用于简化查询代码的语法糖。C#编译器会将查询表达式翻译为对扩展方法的调用。例如查询表达式

var processes =

from process in Process.GetProcesses()

where where process.WorkingSet64 > 20*1024*1024

orderby process.WorkingSet64

select new { process.Id, Name = process.ProcessName };

会被翻译为

var processes =

Process.GetProcesses()

.Where(process => process.WorkingSet64 > 20 * 1024 * 1024)

.OrderBy(process => process.WorkingSet64)

.Select(process => new { process.Id, Name = process.ProcessName });

由于IronPython编译器不支持查询表达式,在IronPython中无法写出SQL-Like的查询语句。但是,利用powershell.py(在IronPython自带的Tutorial目录下可以找到该文件)所提供的思路,我们可以写出“流水线”风格的查询。

processes = (

From(Process.GetProcesses())

.Where(lambda p: p.WorkingSet64 > 20*1024*1024)

.OrderBy(lambda p: p.WorkingSet64)

.Select(lambda p: makeobj(Id = p.Id, Name = p.ProcessName))

)

流水线是一种非常有用的“隐喻”,在Unix Shell、Windows PowerShell等环境中得到广泛的使用。程序员们熟悉它、喜爱它。更重要的是,它符合LINQ流式供应、延迟求值(deferred evaluation)的特点。从某种角度,它比查询表达式更好地表现了查询操作的语义。

在本系列的第三篇文章中,我将基于已有的讨论给出完整的解决方案,在IronPython中提供流水线风格的LINQ to Objects查询。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: