zoukankan      html  css  js  c++  java
  • IronPython and LINQ to Objects (IV): 实现IEnumerable<Object>

    在本系列的前三篇文章中,我介绍了如何用IronPython模拟C#的语言特性如何在IronPython中创建LINQ查询如何在IronPython中实现流水线风格的LINQ to Objects查询。本文将根据IronPython 2.6来进一步完善linq.py,并介绍Iterator模式在Python和C#中的应用,以及如何在IronPython中实现C#接口。

    1. 问题

    LINQ to Object要求被查询的对象实现了System.Collections.Generic.IEnumerable<T>接口。IronPython提供的标准容器列表(list)、元组(tuple)、集合(set)都实现了IEnumerable<Object>,可以被LINQ to Object的查询操作符(定义在System.Linq.Enumerable的扩展方法)直接使用。然而,字典(dict)和其他一些可迭代的对象(如函数open()返回的File对象)并没有实现IEnumerable<Object>。

    为了将IronPython中的可迭代对象配接到IEnumerable<T>接口,LINQ构建块一文建议使用生成器(Generator)。这个建议对于IronPython 2.6 Beta 2 (2.6.0.20)是正确的,对于IronPython 2.6 Final (2.6.10920.0)是错误的。

    1:  import System
    2:  ie_object = System.Collections.Generic.IEnumerable[System.Object]
    3:  generator = (i for i in range(1))

    4:  print isinstance(generator, ie_object)   

    对于上述程序,IronPython 2.6 Beta 2的输出是True,表明generator实现了接口IEnumerable<Object>。但是,IronPython 2.6 Final 输出的是False,表明generator没有实现该接口。可见,linq.py需要一种新的方法来配接IEnumerable<Object>。

    在Python中,可迭代对象必须支持迭代协议(iteration protocal,实现函数__iter__())或序列协议(sequence protocol,实现函数__getitem__())。本文给出了一种方法,将支持迭代协议的对象配接到接口IEnumerable<Object>。该方法很容易扩展到配接支持序列协议的对象。

    2. 迭代协议(iteration protocal

      

    支持迭代协议的对象必须实现函数__iter__(),该函数返回一个迭代子对象(iterator)。迭代子对象必须实现函数next()。每调用一次next(),该函数就返回一个可访问的元素(element)。当迭代结束时,next()不返回元素,而是抛出StopIteration异常。这就是GoF的迭代子模式(Iterator Pattern)在Python语言中的实现。

    Python对于迭代子模式提供了语言级别的支持。任何一个支持迭代协议的对象,都可以被用于for语句和其他迭代表达式中。

    1:  # seq supports iteration protocal
    2:  for i in seq:
    3:      do_something_to(i)
    4:     
    5:  # the implementation of for-statement
    6:  fetch = iter(seq)
    7:  while True:
    8:      try:
    9:          i = fetch.next()
    10:      except StopIteration:
    11:          break
    12:      do_something_to(i)

     

    • 第2~3行:for语句迭代seq,并访问其元素。其中,seq实现了迭代协议。
    • 第6~12行:Python引擎对for语句的一种可能的实现方式。
      • 第6行:iter(seq)等价于seq.__iter__()。该函数调用获得seq的迭代子,并将其赋给fetch。
      • 第9行:调用迭代子的next(),获得可访问的元素。
      • 第10、11行:当next()抛出StopIteration异常时,捕获该异常,利用break语句退出while循环。

      

    3. IEnumerable<T>

      

    Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson一文中,Richard Helm提到

    I think there has been an evolution in level of sophistication. Reusable software has migrated to the underlying system/language as toolkits or frameworks—and mostly should be left to the experts. 我认为,复杂性的层次发生了演变。可复用软件已经以工具包或框架的形式迁移到底层系统和语言中——大部分工作应该由专家完成。

    这一趋势在C#和.NET这样快速演进的语言和平台中得到印证。例如,C#通过IEnumerable<T>接口,对迭代子模式提供了语言级别的支持。

    1:  public interface IEnumerable<T>: IEnumerable
    2:  {
    3:      IEnumerator<T> GetEnumerator();
    4:  }
    5:   
    6:  public interface IEnumerator<T> : IEnumerator, IDisposable
    7:  {
    8:      T Current { get; }
    9:  }
    10:   
    11:  public interface IEnumerator
    12:  {
    13:      bool MoveNext();
    14:      object Current { get; }
    15:      void Reset();
    16:  }
    17:   
    18:  public interface IDisposable
    19:  {
    20:      void Dispose();
    21:  }


    以上就是C#中一个强类型的迭代器所需要实现的4个接口。一个实现了IEnumerable<T>的对象,提供了函数GetEnumerator(),它返回一个实现了IEnumerator<T>的迭代子对象。调用迭代子对象的函数MoveNext(),可以将迭代子移动到下一个元素,并可以利用属性Current访问该元素。当没有更多的元素可以访问时,MoveNext()返回false,表示迭代结束。以下代码展示了C#的foreach语句是如何迭代一个实现了IEnumerable<T>的对象。

    1:  // container implements IEnumerable<T>
    2:  foreach (var elem in container)
    3:  {
    4:      DoSomethingTo(elem);
    5:  }
    6:   
    7:  // the implementation of foreach-statement
    8:  var foreach enumerator = container.GetEnumerator();
    9:  while (enumerator.MoveNext())
    10:  {
    11:      var elem = enumerator.Current;
    12:      DoSomethingTo(elem);
    13:  }

    • 第2~5行:foreach语句迭代container,并访问其元素。其中,container实现了接口IEnumerable<T>。
    • 第8~13行:C#编译器对foreach语句的一种可能的实现方式。
      • 第8行:调用IEnumerable<T>.GetEnumerator()获得实现了IEnumerator<T>的迭代子。
      • 第9行:调用IEnumerator.MoveNext(),将迭代子移动到下一个位置。如果MoveNext()返回false,则迭代结束。
      • 第11行:访问属性IEnumerator<T>.Current获得当前元素。

    由以上代码可见,C#和Python对迭代子模式的实现思路大致相同。其重要区别在于,Python利用异常来跳转控制流,C#则利用MoveNext()的返回值对控制流进行分支选择。这体现了动态语言和静态语言对于异常的不同态度。

     

    4. 将支持序列协议的IronPython对象配接到IEnumerable<T>

    我利用配接器模式(Adapter Pattern),将支持序列协议的IronPython对象配接到IEnumerable<T>。

    1:  class IEnumerableAdapter(System.Collections.Generic.IEnumerable[object],
    2:      System.Collections.Generic.IEnumerator[object]):
    3:      def __init__(self, iteration):
    4:          self.iter = iter(iteration)
    5:          self.element = None
    6:         
    7:      def GetEnumerator(self):
    8:          return self
    9:   
    10:      def get_Current(self):
    11:          return self.element
    12:         
    13:      def MoveNext(self):
    14:          try:
    15:              self.element = self.iter.next()
    16:              return True
    17:          except StopIteration:
    18:              return False
    19:     
    20:      def Dispose(self):
    21:          pass

    感谢IronPython Team的卓越工作,在IronPython中实现C#接口是一件很容易的事情。

    • 第1~2行:定义了类IEnumerableApater,它继承了接口IEnumerable[object]和IEnumerator[object]。在IronPython中,object是System.Object,IEnumerable[object]等价于C#中的IEnumerable<Object>。
    • 第3~5行:定义了初始化函数__init__()。它接纳一个支持迭代协议的对象iteration,调用iter(iteration)获得iteration的迭代子,并保持在self.iter中。
    • 第7~8行:实现了IEnumerable<Object>.GetEnumerator()函数。该函数返回对象自身。这是一个Python的惯用法:用同一个对象实现可迭代对象和迭代子对象。
    • 第10~11行:实现了IEnumerable<Object>.Current属性。C#中的属性一般由get操作和set操作组成。IEnumerable<T>只要求实现Current属性的get操作,所以只要在IEnumerableAdapter定义函数get_Current()即可。该函数返回当前可访问的元素self.element。
    • 第13~18行:实现了IEnumerable.MoveNext()函数。它调用迭代子self.iter的next()函数,以设置当前可访问元素self.element。如果存在可访问元素,返回True。如果不存在可访问元素,next()会抛出StopIteration异常。该异常被捕获后,MoveNext()返回False,表示迭代应该结束。
    • 第20~21行:实现了IDisposable.Dispose()函数。

    值得注意的是,IEnumerableApater没有实现IEnumerator.Reset()函数。在C#中,如果类不实现接口中所有的成员,那么无法实例化该类的对象。IronPython则没有这个限制。IEnumerator的客户可以正常访问IEnumerableApater提供的MoveNext()和get_Current()。如果客户访问IEnumerator.Reset(),将遭遇运行时异常。由于LINQ查询操作符不调用IEnumerator.Reset(),因此IEnumerableApater不实现该函数是可行的。

    将IEnumerableApater加入linq.py。在IronPython控制台(console)上测试该类,可知它满足我的需求。

    >>> import linq

    >>> d = d = {'1':1, '2':2, '3':4, '4':5}

    >>> linq.is_enumerable(d)

    False

    >>> a = linq.IEnumerableAdapter(d)

    >>> linq.is_enumerable(a)

    True

    >>> for key in a: print key, d[key]    # IronPython can iterate IEnumerable.

    ...

    4 5

    3 4

    2 2

    1 1

     

    5. 应用linq.py

    基于IEnumerableApater,将linq.py中的From()修改为:

    1:  def From(col):
    2:      if not is_enumerable(col):
    3:          col = IEnumerableAdapter(col)
    4:      return LinqAdapter(col)


    这样,就可以用LINQ操作符去处理字典和其他可迭代对象了。在以下代码中,query筛选出字典d中键(key)大于2的项(item)所对应的值(value)。最后的输出结果是4和5。

    1:  d = {'1':1, '2':2, '3':4, '4':5}
    2:  query = (
    3:      linq.From(d)
    4:      .Where(lambda key: int(key) > 2)
    5:      .Select(lambda key: d[key])
    6:      )
    7:      
    8:  for i in query:
    9:      print i

    大功告成!linq.py的完整代码可以从这里下载。

     

    6. Q & A

    回答网友DS所提出的一些关于linq.py的问题。

    Q: 我看到IronPython and LINQ to Objects(III),是不是也相当于用IronPython完成了Linq的句法?
    A: 《LINQ in Action》指出,LINQ由两个互为补充的部分组成:处理数据的工具和对编程语言的扩展。

    所谓“处理数据的工具”是.NET Framework提供的一组程序库(例如,定义在System.Linq.Enumerable的扩展方法)和一些代码生成工具。这些程序库和工具可以被.NET平台上的语言所共享。C#、VB.NET、F#或IronPython都可以使用它们。

    但是,“使用”并不代表“好用”。为了提高LINQ的可用性,C#和VB.NET对编程语言进行了扩展。这就是互为补充的第二部分:“语言扩展”。C# 3.0可以使用类似SQL的语法去调用程序库,这极大地简化了程序员的工作。这种类似SQL的语法被称为“查询表达式”。不幸的是,这种语言扩展只在C#和VB.NET被实现。其他.NET平台上的语言都不支持查询表达式。

    linq.py是利用Python语法的灵活性来调用.NET Framework提供的LINQ程序库。它没有实现C#中的“查询表达式”。从这个角度,它没有实现C#的Linq语法。

     

    Q: 更多的感觉是用IronPython完成了一种“包装”?
    A: linq.py是一种“包装”。在IronPython中,直接调用System.Linq.Enumerable的函数是比较繁琐的。linq.py可以视作Facade模式的一个应用:它提供了一个简化的接口来调用System.Linq.Enumerable的函数。

     

    Q: 使得开发者可以用IronPython来写效果等同于C# Linq?
    A: 对于LINQ to Object,IronPython和C#都是调用System.Linq.Enumerable来完成计算。从这个角度,两者的计算基础是相同的,计算结果也是相同的。

     

    Q: 既然MS提出IronPython为什么不完成给出像类似于您的Linq.py呢?
    A: IronPython团队的任务是在.NET平台上构建符合Python标准的Python语言实现,其余的大部分工作应该由IronPython社区来完成。也就是说,他们的任务是构建良好的IronPython引擎,使得linq.py这样的模块可以很容易实现。我觉得,他们的工作很棒!此外,IronPython的官方网站www.ironpython.net(国内访问有困难)有一篇关于IronPython和.NET集成的文档。非常值得一读。

  • 相关阅读:
    前端代码规范
    AD 对联
    leaflet 入门笔记
    在Mac OS X上安装Git
    在Mac上开启C的新征程
    Python编程基础
    GitHub的使用(Git Shell)
    《网页设计心理学》Susan M.Weinschenk 小结精粹
    【问题】做图片验证码时乱码了,在header前加上ob_clean()就能神奇的显示?!
    --allow-file-access-from-files 命令的使用
  • 原文地址:https://www.cnblogs.com/liangshi/p/1726408.html
Copyright © 2011-2022 走看看