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确实是值得关注的动态语言。
 

原文地址:https://www.cnblogs.com/liangshi/p/1726405.html