Python列表理解与.NET LINQ

时间:2010-10-13 15:02:37

标签: c# python linq list-comprehension

以下简单的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();

可以使用list comprehension转换为python,如下所示。

words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = [x for x in words if len(x) <=5]
print shortWords
  • LINQ是另一个实现列表理解的想法吗?
  • LINQ可以做什么样的例子但是列表理解不能做。

4 个答案:

答案 0 :(得分:58)

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

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

首先关闭:Afaik,LINQ查询被懒惰地执行 - 如果是这样的话,生成器表达式是一个更接近的Python概念(无论是哪种方式,列表,字典和集合理解在概念上只是生成器表达式被馈送到list / dict /设置构造函数!)。

此外,还有一个概念上的区别:正如名称所示,LINQ用于查询数据结构。 List- / dict- / set comprehensions可以应用于此(例如,过滤和投影列表的项目)。所以它们实际上不那么通用(正如我们将看到的,LINQ中内置的很多东西都不是内置的)。同样,生成器表达式是一种在某种程度上形成一次性前向迭代器的方法(我喜欢将它视为生成器函数的lambda,只有没有丑陋的long关键字;))而不是描述复杂查询的方法。它们重叠,是的,但它们并不相同。如果你想在Python中使用LINQ的所有功能,你将不得不编写一个完整的生成器。或者将itertools内置的众多强大的生成器组合在一起。


现在,用于LINQ功能的Python同行Jon Skeet命名为:

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

过滤:(... if x.bar > 5)

  
      
  • 加入(x在y.foo上加入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连接y在x.foo上等于y.bar到g)
  •   

这更难。 Python没有匿名类型,但如果你不介意弄乱__dict__,那么你自己做的很简单:

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

然后,我们可以(Anonymous(x=x, y=y) for ...)获取具有相应值的xy成员的对象列表。 正确的做法通常是将结果提供给approriate类的构造函数,比如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()来返回一组没有我们分组的值的可迭代组(当然我们可以将 提供给list得到我们可以索引和迭代几次的东西)。但谁知道我们是否不需要团体价值......

  
      
  • 订购(按x.foo升序排列,y.bar降序排列)
  •   

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

唉,通过多个属性进行分类并不容易,特别是在混合升序和降序时。嗯...食谱的话题?

  
      
  • 中间变量(让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查询。但没有人关心。

答案 1 :(得分:24)

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

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

C#在查询表达式中不像VB那样支持,但这是支持支持的内容:

  • 预测(select x.foo
  • 过滤(where x.bar > 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的重要组成部分。

答案 2 :(得分:16)

通过使用asq Python包,您可以轻松地在Python中执行大部分操作,您可以使用LINQ-for-objects在C#中执行此操作。使用asq,您的Python示例变为:

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

答案 3 :(得分:4)

我不是Python大师,但我会说Python实际上支持所有这些,因为你可以嵌套列表推导并包含你想要的所有lambda表达式。 (如果列表理解过于复杂,那么它们往往难以阅读,但是......)但是没有包含任何“特定语法”来完成所有这些。

大多数功能可以使用以下方式复制:   - 列表推导生成器   - lambda函数或内置函数(如filter()map())或itertools模块中的函数

例如,如果要复制以下行为:

  • 预测:这将是列表理解的左侧部分......可以是单个值,也可以是元组。例如:[ (k,v) for k,v in my_dict.items() if k.startswith("abc"]。您也可以使用map()
  • 过滤:这将是if之后右侧的表达式。您也可以使用filter()
  • 订购:只需使用内置的sorted()
  • 即可
  • 分组汇总:使用内置min()max()itertools.groupby()

关于加入展平,我认为你必须“手工完成”......

(总是很高兴Python Quick Reference触手可及)