zoukankan      html  css  js  c++  java
  • IronPython and LINQ to Objects (II): LINQ 构建块

    在第一篇文章中,我讨论了如何用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查询。

  • 相关阅读:
    ZooKeeper 到底解决了什么问题?
    10个 Linux 命令,让你的操作更有效率
    你的镜像真的安全吗?
    谁动了我的主机? 之活用History命令
    Linux vs Unix
    Linux应急响应入门——入侵排查
    (一)Sharding-JDBC分库分表概述
    (三)微服务的注册中心—Eureka
    (二)springcloud功能组件
    (一)微服务基础
  • 原文地址:https://www.cnblogs.com/liangshi/p/1726397.html
Copyright © 2011-2022 走看看