【问题标题】:Python's list comprehension vs .NET LINQPython 列表理解与 .NET LINQ
【发布时间】:2023-04-08 03:38:01
【问题描述】:

以下简单的LINQ代码

string[] words = { "hello", "wonderful", "linq", "beautiful", "world" };

// Get only short words
var shortWords =
  from word in words
  where word.Length <= 5
  select word;

// Print each word out
shortWords.Dump();

可以如下使用列表推导翻译成python。

words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = [x for x in words if len(x) <=5]
print shortWords
  • LINQ 只是实现列表理解的另一个想法吗?
  • 哪些示例可能是 LINQ 可以执行但列表解析无法执行。

【问题讨论】:

  • 如果您在 C# 中比较懒惰,请考虑改为:words.Where(w => w.Length
  • 正如this epic explanation of monads 中所解释的,LINQ 被精心设计为单子,因此可以以简单、安全和有效的方式构建它。 Monad 和函数式编程的重要性正在迅速增加。所以我建议人们在他们的回答中解决这个问题。
  • @nealmcb monad 在不支持共享可变状态的语言中唯一重要,就像静态方法仅在不支持一流函数的语言中有用一样。这是一种强制意识形态纯洁性的方法,以允许编程范式做一些它不太适合的事情,只是为了完成。因此,monad 的重要性永远不会增加。绕过自我强加的限制是一个拐杖。泛型与动态类型是另一个例子。
  • @Davor 没有数据的类是不合逻辑的;对象是数据和作用于它们的方法。静态方法只是充当“漏洞”,以允许在意识形态纯洁性禁止此类事情的语言中使用独立功能。如果我可以在任何地方定义一个一流的函数,我就不需要把它粘在一个类中;我可以在没有它们的情况下用静态方法编写任何东西,而不会失去表达力或简洁性。它们在这种情况/语言中毫无用处。
  • @alcalde - 当您拥有仅在一个类中需要的第一类函数时,您看不到将其设为该类的私有静态并以这种方式限制其可见性有何意义?

标签: c# python linq list-comprehension


【解决方案1】:

(警告:前面的猛犸回答。第一条水平线之前的部分是一个很好的 tl;dr 部分,我想)

我不确定我是否有资格成为 Python 大师......但我对 Python 中的迭代有很好的掌握,所以让我们试试吧 :)

首先:Afaik,LINQ 查询是延迟执行的——如果是这种情况,生成器表达式是更接近 Python 的概念(无论哪种方式,list-、dict- 和集合推导在概念上只是输入到 list/dict/ 的生成器表达式设置构造函数!)。

此外,还有一个概念上的区别:LINQ 顾名思义,用于查询数据结构。 List-/dict-/set 理解是可能的应用(例如过滤和投影列表的项目)。所以它们实际上不那么通用(正如我们将看到的,许多内置在 LINQ 中的东西并没有内置在它们中)。同样,生成器表达式是一种就地制定一次性前向迭代器的方法(我喜欢将其视为生成器函数的 lambda,只是没有丑陋的长关键字;),而不是描述复杂查询的方法.它们重叠,是的,但它们并不相同。如果您想在 Python 中使用 LINQ 的所有功能,则必须编写一个成熟的生成器。或者结合itertools中内置的众多强大的生成器。


现在,LINQ 功能的 Python 对应物 Jon Skeet 命名为:

预测:(x.foo for ...)

过滤:(... if x.bar &gt; 5)

  • 联接(x.foo 上的 x 联接 y 等于 y.bar)

我想最接近的应该是((x_item, next(y_item for y_item in y if x_item.foo == y_item.bar)) for x_item in x)

请注意,这不会遍历每个 x_item 的整个 y,它只会获得第一个匹配项。

  • 组连接(x 在 x.foo 上连接 y 等于 y.bar 到 g)

这更难。 Python 没有匿名类型,但如果您不介意与 __dict__ 混淆,那么您自己做这些是微不足道的:

class Anonymous(object):
    def __init__(self, **kwargs):
        self.__dict__ = kwargs

然后,我们可以使用(Anonymous(x=x, y=y) for ...) 来获取具有xy 成员以及各自值的对象列表。 正确的做法通常是将结果提供给适当类的构造函数,例如 XY。

  • 分组(按 x.bar 分组 x.foo)

现在它变得毛茸茸的......没有内置的方式,afaik。但如果需要,我们可以自己定义:

from collections import defaultdict

def group_by(iterable, group_func):
    groups = defaultdict(list)
    for item in iterable:
        groups[group_func(item)].append(item)
    return groups

例子:

>>> from operator import attrgetter
>>> group_by((x.foo for x in ...), attrgetter('bar'))
defaultdict(<class 'list'>, {some_value_of_bar: [x.foo of all x where x.bar == some_value_of_bar], some_other_value_of_bar: [...], ...})

不过,这要求我们分组的任何内容都是可散列的。可以避免这种情况,如果有公众需求,我会做一个刺。但是现在,我很懒惰:)

我们也可以通过在结果上调用 .values() 来返回一个没有分组值的组的可迭代(当然我们可以将 that 提供给 list 以获得我们可以得到的东西索引并迭代几次)。但是谁知道我们是否不需要组值...

  • 排序(orderby x.foo 升序,y.bar 降序)

排序需要特殊的语法?内置 sorted 也适用于可迭代对象:sorted(x % 2 for x in range(10))sorted(x for x in xs, key=attrgetter('foo'))。默认升序排序,关键字参数reverse给出降序排列。

唉,afaik 按多个属性排序并不是那么容易,尤其是在混合升序和降序时。嗯……食谱的主题?

  • 中间变量(让 tmp = x.foo)

不,在推导式或生成器表达式中是不可能的——顾名思义,它们应该是表达式(通常只跨越一两行)。不过,在生成器函数中这是完全可能的:

(x * 2 for x in iterable)

用中间变量重写为生成器:

def doubles(iterable):
    for x in iterable:
        times2 = x * 2
        yield times2

扁平化:(c for s in ("aa","bb") for c in s )


请注意,尽管 LINQ to Objects 处理委托,但其他查询提供程序(例如 LINQ to SQL)可以处理描述查询的表达式树,而不仅仅是呈现可执行委托。这允许将查询翻译成 SQL(或其他查询语言)——同样,我不知道 Python 是否支持这种事情。不过,它是 LINQ 的重要组成部分。

Python 绝对不会做这样的事情。列表表达式与在(可能嵌套的)for循环中累积一个普通列表一一对应,生成器表达式与生成器一一对应。 鉴于 parserast 模块,理论上可以编写一个库来将理解转换为例如SQL 查询。但没人在乎。

【讨论】:

  • 要使其中一些更“Pythonic”(即原生/惯用),请查看 Python 的 collections.namedtupleitertools.groupby
  • 也用于展平:itertools.chain
【解决方案2】:

嗯,你需要区分一些不同的东西:

  • LINQ 标准查询运算符
  • C# 中的 LINQ 查询表达式
  • VB 中的 LINQ 查询表达式

C# 对查询表达式的支持不如 VB,但它支持 如下:

  • 预测 (select x.foo)
  • 过滤 (where x.bar &gt; 5)
  • 加入 (x join y on x.foo equals y.bar)
  • 群组加入 (x join y on x.foo equals y.bar into g)
  • 分组 (group x.foo by x.bar)
  • 订购 (orderby x.foo ascending, y.bar descending)
  • 中间变量 (let tmp = x.foo)
  • 展平 (from x in y from z in x)

我不知道其中有多少是直接在 Python 的列表推导中得到支持的。

请注意,尽管 LINQ to Objects 处理委托,但其他查询提供程序(例如 LINQ to SQL)可以处理描述查询的表达式树,而不仅仅是呈现可执行委托。这允许将查询翻译成 SQL(或其他查询语言)——同样,我不知道 Python 是否支持这种事情。不过,它是 LINQ 的重要组成部分。

【讨论】:

  • “你能做的任何事,我都能做得更好......”请参阅 pynq (github.com/heynemann/pynq/wiki)。为什么不将橙子与橙子进行比较?无论如何,列表推导无法与 LINQ 相提并论,因为它们的用途完全不同。 LINQ 强调表现力而不是性能,列表推导式强调性能而不是表现力。它们的工作方式类似于您通过传入 IEqualsComparator 在 C# 中覆盖 .Equals() 方法的方式,不同之处在于您将生成器函数传递给迭代器的构造函数(例如列表)。我不是大师,但这是 python 中非常基本的东西。
  • @EvanPlaice 列表推导不是生成器,区别在于使用方括号与圆括号参见stackoverflow.com/questions/47789/…,而在 .Net Linq 中始终在内存中,可以使用 yield 关键字生成(类似于生成器,但没有一次性使用限制)
【解决方案3】:

通过使用asq Python 包,您可以轻松地在Python 中完成大多数可以在C# 中使用LINQ-for-objects 完成的事情。使用 asq,您的 Python 示例变为:

from asq.initiators import query
words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = query(words).where(lambda x: len(x) <= 5)

【讨论】:

    【解决方案4】:

    我不是 Python 专家,但我想说 Python 实际上支持所有这些,因为您可以嵌套列表推导并包含您想要的所有 lambda 表达式。 (如果列表理解过于复杂,则往往难以阅读......),但不,它不包括完成所有这些的“特定语法”。

    大部分功能都可以通过以下方式重现: - 列表推导生成器 - lambda 函数或内置函数(如 filter()map())或来自 itertools 模块的函数

    例如,如果你想复制以下行为:

    • Projections :这将是列表理解的左侧部分......它可以是单个值,也可以是元组。例如:[ (k,v) for k,v in my_dict.items() if k.startswith("abc"]。您也可以使用map()
    • Filtering :这将是右边的表达式,在if 之后。您也可以使用filter()
    • Ordering :只需使用内置的sorted()
    • Groupingaggregates :使用内置的 min()max()itertools.groupby()

    关于 joinsflattening,我认为您必须“手动完成”...

    (总是很高兴有Python Quick Reference

    【讨论】:

      猜你喜欢
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 2016-10-04
      • 1970-01-01
      • 2011-09-02
      • 2020-11-09
      • 1970-01-01
      • 2022-12-13
      相关资源
      最近更新 更多