Python:支持LINQ的类同时支持懒惰和流畅的设计

时间:2016-08-25 20:31:37

标签: python linq iterator generator

如何使用类似LINQ的方法(select,where,orderby等)创建一个Python类,它是一个可迭代的包装器,而不使用扩展方法或猴子修补。 ?

这就是这个LinqCapable类能够在相关时返回它自己的类型(即流畅的设计)并支持延迟评估。

我只是在这里寻找一个片段作为起点。

2 个答案:

答案 0 :(得分:1)

你应该返回一个“Linq能力类”来实现链接,而且你的实现并不是懒惰的:看看asq where method:它基于I过滤器,看起来是正确的...... 无论如何,根据我对你的问题和评论的理解,这是一个非常基本的实现

Location()

用法

class LinqCapable(object):
    def __init__(self, iterable=None):
        self._iterable = iterable
        self._predicates = []
    def where(self, predicate):
        chain = LinqCapable(self._iterable)
        chain._predicates = self._predicates
        chain._predicates.append(predicate)
        return chain
    def toArray(self):
        for item in self._iterable:
          isOk = True
          for predicate in self._predicates:
            if (not predicate(item)):
                isOk = False
                break

          if (isOk):
              yield item

编辑(添加选择方法)

我还添加了一个简单的select方法。 这里的目标是在可能的情况下聚合谓词和选择器,以避免低效的嵌套循环。

test = LinqCapable([1,2,3])
def pred1(l: int) -> bool:
   return l>1
chain1 = test.where(pred1)
def pred2(l: int) -> bool:
   return l<3
chain2 = chain1.where(pred2)
list(chain2.toArray())

所以一个例子是

class LinqCapable(object):
    def __init__(self, iterable=None, predicates = [],  selectors = [],  tree=None):
        self._iterable =  iterable
        self._predicates = list(predicates)
        self._selectors = list(selectors)
        self._tree = tree
    def select(self,  selector):
        if (len(self._predicates) == 0):
            chain = LinqCapable(self._iterable,  [], self._selectors,  self._tree)
            chain._selectors.append(selector) 
        else:
            chain = LinqCapable(None,  [],  [])
            if (len(self._selectors) == 0):
                chain._tree = LinqCapable(self._iterable,  self._predicates,  [],  self._tree)
            else:
                chain._tree = self
            chain._selectors.append(selector) 
        return chain
    def where(self, predicate):
        chain = LinqCapable(self._iterable,  self._predicates,  self._selectors,  self._tree)
        chain._predicates.append(predicate)
        return chain
    def enumerate(self):
        if  (self._tree != None):
            self._iterable = list(self._tree.enumerate())
        return self._cycle()
    def _cycle(self):
        for item in self._iterable:
          for selector in self._selectors:
            item = selector(item)
          isOk = True
          for predicate in self._predicates:
            if (not predicate(item)):
                isOk = False
                break
          if (isOk):
              yield item

答案 1 :(得分:1)

仅为实现的linq方法返回一个生成器是不够的,你需要让它返回一个包装器的实例,以便能够链接其他调用。

您可以创建一个可以重新封装linq实现的元类。因此,您可以实现您想要支持的方法,并使用一些特殊的装饰器来确保它可以链接。

def linq(iterable):
    from functools import wraps
    def as_enumerable(f):
        f._enumerable = True
        return f
    class EnumerableMeta(type):
        def __new__(metacls, name, bases, namespace):
            cls = type.__new__(metacls, name, bases, namespace)
            def to_enumerable(f):
                @wraps(f)
                def _f(self, *args, **kwargs):
                    return cls(lambda: f(self, *args, **kwargs))
                return _f
            for n, f in namespace.items():
                if hasattr(f, '_enumerable'):
                    setattr(cls, n, to_enumerable(f))
            return cls
    class Enumerable(metaclass=EnumerableMeta):
        def __init__(self, _iterable):
            self._iterable = _iterable
        def __iter__(self):
            return iter(self._iterable())
        @as_enumerable
        def intersect(self, second):
            yield from set(self._iterable()).intersection(second)
        @as_enumerable
        def select(self, selector):
            yield from map(selector, self._iterable())
        @as_enumerable
        def union(self, second):
            yield from set(self._iterable()).union(second)
        @as_enumerable
        def where(self, predicate):
            yield from filter(predicate, self._iterable())
        @as_enumerable
        def skip(self, count):
            yield from (x for x, i in enumerate(self._iterable()) if i >= count)
        @as_enumerable
        def skip_while(self, predicate):
            it = iter(self._iterable())
            for x in it:
                if not predicate(x):
                    yield x
                    break
            yield from it
        @as_enumerable
        def take(self, count):
            yield from (x for x, i in enumerate(self._iterable()) if i < count)
        @as_enumerable
        def take_while(self, predicate):
            for x in self._iterable():
                if not predicate(x): break
                yield x
        @as_enumerable
        def zip(self, second, result_selector=lambda a, b: (a, b)):
            yield from map(lambda x: result_selector(*x), zip(self._iterable(), second))
        def single(self, predicate=lambda _: True):
            has_result = False
            for x in self._iterable():
                if predicate(x):
                    if has_result:
                        raise TypeError('sequence contains more elements')
                    value = x
                    has_result = True
            if not has_result:
                raise TypeError('sequence contains no elements')
            return value

        def sum(self, selector=lambda x: x):
            return sum(map(selector, self._iterable()))
        def to_dict(self, key_selector, element_selector=lambda x: x):
            return {
                (key_selector(x), element_selector(x))
                for x in self._iterable()
            }
        def to_list(self):
            return list(self._iterable())
    return Enumerable(lambda: iterable)

因此,您可以使用任何可迭代序列执行此类操作,就像在C#中执行此操作一样。

# save a linq query
query = linq(range(100))

# even numbers as strings
evenstrs = query.where(lambda i: i%2 == 0).select(str)

# build a different result using the same query instances
odds = query.where(lambda i: i%2 != 0)
smallnums = query.where(lambda i: i < 50)
# dynamically build a query
query = linq(some_list_of_objects)

if some_condition:
    query = query.where(some_predicate)

if some_other_condition:
    query = query.where(some_other_predicate)

result = query.to_list()