Python 列表推导与 .NET LINQ
下面这段简单的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可以做到,但列表推导式做不到的呢?
4 个回答
通过使用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)
好吧,你需要区分一些不同的东西:
- 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的一个重要部分。
(警告:接下来是个大长篇。第一部分到第一条横线的内容可以算作简要概述,应该还不错)
我不确定自己是否算得上Python高手……但我对Python中的迭代有一定了解,所以我们来试试吧 :)
首先:据我所知,LINQ查询是懒执行的——如果是这样的话,生成器表达式在Python中更接近这个概念(无论如何,列表、字典和集合推导本质上就是将生成器表达式传递给列表、字典或集合构造函数!)。
另外,还有一个概念上的区别:LINQ是用来查询数据结构的,正如名字所示。列表、字典和集合推导是这种查询的应用(例如,过滤和投影列表中的项目)。所以它们实际上不够通用(正如我们将看到的,很多LINQ内置的功能在这些推导中并不存在)。同样,生成器表达式是一种在本地创建一次性前向迭代器的方法(我喜欢把它看作是生成器函数的lambda,只是没有那么丑、那么长的关键字 ;)),而不是描述复杂查询的方式。它们有重叠,但并不相同。如果你想在Python中使用LINQ的所有功能,你需要写一个完整的生成器,或者结合使用内置的和在itertools
中的众多强大生成器。
现在,Python中与Jon Skeet提到的LINQ功能对应的内容:
投影:(x.foo for ...)
过滤:(... if x.bar > 5)
- 连接(x连接y,条件是x.foo等于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 ...)
来获取一个包含x
和y
成员及其相应值的对象列表。通常正确的做法是将结果传递给一个合适类的构造函数,比如XY。
- 分组(按x.bar分组x.foo)
这就复杂了……据我所知,没有内置的方法。不过如果需要,我们可以自己定义:
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
可以实现降序。
可惜的是,按多个属性排序并不那么简单,尤其是混合升序和降序。嗯……这可以作为一个食谱的主题吗?
- 中间变量(let 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循环中累积一个普通列表一一对应,生成器表达式与生成器一一对应。考虑到parser
和ast
模块,理论上可以写一个库,将推导式转换为例如SQL查询。但没有人愿意去做。