【问题标题】:Python: LINQ capable class supporting simultaneously laziness and fluent designPython:支持 LINQ 的类同时支持惰性和流畅的设计
【发布时间】:2017-01-02 09:59:40
【问题描述】:

如何在不使用扩展方法或猴子补丁的情况下创建一个 Python 类,该类将是一个带有类似 LINQ 的方法(select、where、orderby 等)的可迭代包装器。 ?

也就是说,这个 LinqCapable 类能够在相关时返回自己的类型(即流畅的设计)并支持惰性求值。

我只是在这里寻找一个 sn-p 作为起点。

【问题讨论】:

  • “我不能锁链”是什么意思?您只需要返回一个封装了上述过滤枚举器的新 LinqCapable 对象...还缺少什么?
  • 由于您正在执行 for 循环,因此我看不到您声称的惰性行为。 github 上的 asq 通过 ifilter 实现了懒惰:乍一看,它看起来是正确的。最后注意到 Linq Queryable 是惰性的,而不是 IEnumerable...
  • @Machine Learning 回到 C# var filters = source.where(Whathever);直到我们开始编写类似 var actualFilteredSource = filters.ToArray();因为评估需要生成 ToArray 扩展方法所涉及的集合。
  • 过滤的未评估 iff 源是 IQueryable。怎么做?我会说 Linq 是通过求助于 AST 来做到这一点的,这太复杂而无法重新实现。在 Python afaik 中,您可以像 asq 一样使用 ifilter。更一般地说,我不明白您在 asq 实现中不喜欢什么:您在抱怨扩展,但这正是 linq 方法的含义:-)
  • AFAIK 它确实使用了 AST(可能值得看看 Github 上的 MS repos,因为它们是公开的)。为什么要打扰自己?只是为了在没有扩展方法的情况下重新发明轮子 :) 还有一件事,我不喜欢 asq 只是想自己做事,认为它们会是一些令人讨厌的方法。

标签: python linq iterator generator


【解决方案1】:

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

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

用法

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

举个例子

test = LinqCapable([1,2, 20,200, 300])
def pred1(l: int) -> bool:
   return l>1
chain1 = test.where(pred1)
def pred2(l: int) -> bool:
   return l<300
def sel1(l: int) -> str:
   return str(l)
def sel2(l: str) -> str:
   return '<' + l + '>'
def pred3(l: str) -> bool:
   return len(l) > 3
def sel3(l: str) -> str:
   return l[1:-1]
def sel4(l: str) -> int:
   return int(l)
chain2 = chain1.where(pred2).select(sel1).select(sel2).where(pred3).select(sel3).select(sel4)
print(list(chain2.enumerate()))

【讨论】:

  • 感谢您的反馈 :-) 我现在还添加了一个简单的选择方法...
  • 我看到您已经注意到第一个版本通过引用复制谓词和其他数据,因此创建where 查询(例如)也会修改原始结构。由于原始代码仍在您的帖子中,因此如果他们没有意识到您实际上是从上面的代码块重写了函数,我指出这一点是为了给读者省点麻烦。
  • 用更复杂的例子修复了一个错误
  • @piojo 正确,我可能稍后会加强这个概念,谢谢
【解决方案2】:

仅仅为实现的 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()

【讨论】:

    猜你喜欢
    • 2013-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-25
    • 1970-01-01
    • 2014-12-05
    • 2023-03-04
    相关资源
    最近更新 更多