• IronPython and LINQ to Objects (III): linq.py


    在本系列的前两篇文章中,我介绍了如何用IronPython模拟C#的语言特性如何在IronPython中创建LINQ查询。本文将给出一个IronPython模块linq.py,用于在IronPython中提供流水线风格的LINQ to Objects查询。


    以下就是linq.py的全部代码。

      1: import clr
      2: clr.AddReference('System.Core')
      3:
      4: import System
      5: from System.Linq import Enumerable
      6: from System import Func
      7:
      8:
      9: # Pythonic wrappers around some common linq functions
     10: def Count(col, fun = lambda x: True):
     11:     return Enumerable.Count[object](col, Func[object, bool](fun))
     12:
     13: def OrderBy(col, fun):
     14:     return Enumerable.OrderBy[object, object](col, Func[object, object](fun))
     15:    
     16: def OrderByDesc(col, fun):
     17:     return Enumerable.OrderByDescending[object, object](col, Func[object, object](fun))
     18:
     19: def Select(col, fun):
     20:     return Enumerable.Select[object, object](col, Func[object, object](fun))
     21:
     22: def Single(col, fun):
     23:     return Enumerable.Single[object](col, Func[object, bool](fun))
     24:
     25: def Where(col, fun):
     26:     return Enumerable.Where[object](col, Func[object, bool](fun))
     27:
     28:
     29: # Save all Pythonic wrappers into dict
     30: this_module = __import__(__name__)
     31: linqs = {}
     32: for name in dir(this_module):
     33:     if name.startswith('__') or name in ('this_module', 'linqs'):
     34:         continue
     35:     linqs[name] = getattr(this_module, name)
     36:
     37:
     38: def is_enumerable(obj):
     39:     ienum_object = System.Collections.Generic.IEnumerable[object]
     40:     return isinstance(obj, ienum_object)
     41:
     42:
     43: class LinqAdapter(object):
     44:     def __init__(self, col):
     45:         self.col = col
     46:
     47:     def __iter__(self):
     48:         return iter(self.col)
     49:        
     50:     def __str__(self):
     51:         return '[%s]' % ', '.join( (str(v) for v in self) )
     52:        
     53:     def __repr__(self):
     54:         return str(self)
     55:        
     56:     def __getattr__(self, attr):
     57:         def decorator(*arg, **kws):
     58:             result = linqs[attr](self.col, *arg, **kws)
     59:             if is_enumerable(result):
     60:                 return LinqAdapter(result)
     61:             else:
     62:                 return result
     63:         return decorator
     64:
     65:
     66: def From(col):
     67:     if (is_enumerable(col)):
     68:         return LinqAdapter(col)
     69:     else:
     70:         return LinqAdapter( (c for c in col) )

    第一部分,加载程序集,导入名字。
    • 第1~2行:从GAC中加载System.Core.dll。该程序集定义了LINQ to Object的查询操作符,它们都是定义在System.Linq.Enumerable中的扩展方法。
    • 第4~6行:导入名空间System,导入类Enumerable和Func
    • 第9~26行:定义了一组辅助函数,以方便调用查询操作符。Enumerable中的扩展方法很多,这里只是示例性地提供了6个辅助函数。读者可以定义更多的辅助函数,以调用其他扩展方法。

    第二部分,将辅助方法加入字典对象linqs中。其中,字典的键是辅助函数名(如“Where”),对应的值是方法对象(如Where)。
    • 第30行:获取当前模块(即linq.py)的引用:__name__返回当前模块的名字,__import__(__name__)返回当前模块的引用,该引用保存于this_module。
    • 第31行:创建字典对象linqs。
    • 第32行:迭代当前模块(this_module)的所有成员的名字(name)。
    • 第33~34行:如果name代表内建函数(以"__"开始)或字典对象(linqs),则忽略该name。 由于linq.py在该语句之前只定义了一批辅助函数,因此没有被过滤的name对应于辅助函数名。
    • 第35行:getattr(this_module, name)获取name对应的辅助函数,并保存在字典项linqs[name]中。

    第三部分,定义辅助方法is_enumerable和辅助类LinqAdapter。
    • 第38~40行:定义辅助方法is_enumerable,用于判断一个对象是否实现了接口System.Collections.Generic.IEnumerable[object]。这样的对象可以直接传递给查询操作符。
    • 第43行:定义了类LinqAdapter
    • 第44~45行:实现了初始化方法。客户端程序需要保证输入参数col是一个实现了接口IEnumerable[object]的对象。
    • 第47~48行:实现了迭代子协议(iterator protocol),使得LinqAdapter对象可以直接用于for语句。函数__iter__的返回值是一个迭代子,它迭代self.col的元素。
    • 第53~54行:实现了特殊函数__repr__,它返回一个字符串。该字符串表示了self.col的所有元素。
    • 第56~63行:实现了特殊函数__getattr__。它根据客户端指定的属性名attr,返回一个函子decorator。对于LinqAdapter对象adapter,表达式adapter.Where(condition)等同于adapter.__getattr__(‘Where’)(condition),等同于Where(adapter.col, condition)。这是一个非常有用的Python惯用法,是构建流水线风格的查询的关键。
    • 第58行:该行要求__getattr__的输入参数attr必须是一个在linqs中可以找到的辅助函数名。如果attr不是辅助函数名,那么该行将抛出异常KeyError。linqs[attr](self.col, *arg, **kws)的操作分为两步。第一步,linqs[attr]返回attr对应的辅助函数。第二步,调用该辅助函数,其中第一个参数是self.col,其余参数由客户端提供。函数调用的返回值保存在result中。
    • 第59~62行:判断result是否实现了接口IEnumerable[object]。如果实现了,则说明result的元素可以继续在流水线中传递,于是将其配接为LinqAdapter(result),并返回配接对象。如果没有实现(通常是Count、Max、Min等查询操作符的返回值),则说明result无法在流水线中传递,直接将其返回。

    第四部分,定义了辅助函数From,配接可迭代对象,返回LinqAdapter对象。
    • 第67行:判断输入参数col是否实现了接口IEnumerable[object]。
    • 第68行:col实现了IEnumerable[object],将其直接传递给LinqAdapter。
    • 第70行:col没有实现IEnumerable[object],用生成器(Generator)(c for c in col) 生迭代子,将迭代子传递给LinqAdapter。

    实现了linq.py,就可以在IronPython中方便地调用LINQ to Objects。首先,导入linq.py,构建辅助类DataObject。
      1: import linq
      2:
      3: class DataObject(object):
      4:     def __init__(self, **kws):
      5:         self.__dict__.update(kws)

    然后,调用linq.From获得LinqAdapter对象。利用该对象构建流水线风格的查询。
      1: import System
      2: from System.Diagnostics import Process
      3:
      4: processes = (
      5:     linq.From(Process.GetProcesses())
      6:     .Where(lambda p: p.WorkingSet64 > 20*1024*1024)
      7:     .OrderBy(lambda p: p.WorkingSet64)
      8:     .Select(lambda p: DataObject(Id = p.Id, Name = p.ProcessName))
      9:     )
     10:
     11: for p in processes:
     12:     print p.Name, p.Id
     13:
     14: print processes.Count()

    • 第1~2行:导入名空间System和类Process。
    • 第4~9行:构建LINQ to Objects查询,将查询保存在processes中。
    • 第5行:获得LinqAdapter对象,该对象的成员col引用了Process.GetProcesses()的返回值(一个Process数组)。
    • 第6行:调用Where辅助函数,其过滤条件是进程的WorkingSet64大于20M。
    • 第7行:调用OrderBy辅助函数,按照WorkingSet64的大小排序。
    • 第8行:调用Select辅助函数,生成新的对象。该对象的数据成员Id对应进程号,数据成员Name对应进程名。
    • 第11~12行:执行查询processes,并输出进程名和进程Id。
    • 第14行:再次执行查询processes,输出满足过滤条件的进程个数。

    从这个例子可以看出,linq.py使得IronPython程序员可以方便地构造LINQ查询。这种流水线风格的语法,虽然没有C#和VB.NET中的查询表达式简单,但也非常直观、易懂。对于一些问题,其表达力甚至强于查询表达式。此外,linq.py保留了LINQ流式供应、延迟求值的特点,充分发挥了LINQ to Objects的能力。

    由本系列的第二篇文章可知,IronPython的字典(dict)对象没有实现接口IEnumerable[object]。From利用生成器配接字典对象,使字典对象也可以用作LINQ to Objects的数据源。
      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

    • 第1行:构建字典对象d。
    • 第2~6行:构建LINQ to Objects查询,将查询保存在query中。
    • 第3行:获得LinqAdapter对象,该对象的成员col引用了一个迭代子。该迭代子返回字典d的所有键。
    • 第4行:调用Where辅助函数,其过滤条件是键key的字面值大于2。
    • 第5行:调用Select辅助函数,获得键对应的值。
    • 第8~9行:执行查询query,输出4和5。

    实际上,From利用生成器,可以配接IronPython中所有实现了迭代子协议的对象。
      1: class Range(object):
      2:     def __init__(self, start, end):
      3:         assert start < end
      4:         self.start = start
      5:         self.end = end
      6:        
      7:     def next(self):
      8:         if self.start < self.end:
      9:             value = self.start
     10:             self.start += 1
     11:             return value
     12:         else:
     13:             raise StopIteration
     14:        
     15:     def __iter__(self):
     16:         return self
     17:
     18: r = Range(1, 5)
     19: print linq.is_enumerable(r) # print "False"
     20: query = linq.From(r).Where(lambda x: x > 2)
     21: for i in query:
     22:     print i

    • 第1~16行:定义了类Range,它实现了迭代子协议。
    • 第18行:定义了对象r。如果执行for i in r: print i,将输出1, 2, 3, 4。
    • 第19行:检查对象r是否实现了接口IEnumerable[object]。该语句输出False。
    • 第20行:构建LINQ to Objects查询,将查询保存在query中。该查询返回序列r中大于2的值。
    • 第21~22行:执行查询query,输出3和4。

    linq.py展示了IronPython的威力。一方面,Python语言提供了简洁、灵活、有表达力的程序;另一方面,IronPython无缝地与CLR集成,充分地利用.NET的强大能力。对于.NET平台上的程序员,IronPython确实是值得关注的动态语言。
     

  • 相关阅读:
    STL笔记之【map之总概】
    STL笔记之set
    Effective C++笔记之Item49【了解new-handler的行为】
    明成软件条形码打印设置
    将Excel数据导入到SqlServer及导入时数据类型转换失败解决方案
    远程桌面无法复制粘贴传输文件解决办法
    DELPHI如何读取cxcheckcombobox中的值
    Delphi 插入Excel图片和值
    SQL 查询语句先执行 SELECT?
    Linux之xargs命令传递参数的一个过滤器
  • 原文地址:https://www.cnblogs.com/liangshi/p/1726405.html
Copyright © 2020-2023  润新知