在Python中为链式列表表达式更正样式

时间:2013-09-14 17:43:27

标签: python styles list-comprehension

我正在尝试在Python中的一个对象数组上编写一个简单的查询,这在C#或Ruby中是微不足道和优雅的,但是我很难在Python中使它变得优雅。我想我做错了。

在C#中:

list.Where(x => x.Foo > 10).Select(x => x.Bar).Where(x => x.Baz.StartsWith("/"))

这将创建一个枚举,其中包含list[0].Bar提供list[0].Foo> 10和list[0].Bar.Baz'/'开头,依此类推,列表中的所有其他项目。数据清晰地从左向右流动,并且可以在右侧附加进一步的过滤/投影/聚合。

在Ruby中:

list.select { |x| x.foo > 10 }.map(&:bar).select { |x| x.baz.starts_with? '/' }

同样,从左到右是一个相当清晰的流程,可以轻松附加进一步的操作。

但是我在Python中的尝试似乎是倒退的,从内到外都是丑陋的:

[x for x in (x.bar for x in (x for x in list if x.foo > 10)) if x.baz.startswith('/')]

现在我知道我可以在一个步骤中将地图和过滤器与列表理解相结合,并且上面的内容可以重写为:

[x.bar for x in list if x.foo > 10 and x.bar.baz.startswith('/')]

但这反过来忽略了这一点。一方面,投影x.bar可能很昂贵,而且我不想评估它两次;另一方面,投影和过滤只是我应用于流的两个潜在操作,我可以进行排序,聚合,分页等,并不是所有的投影和过滤器都需要相邻,也不需要在投影之前应用过滤器而不是之后。

我是否试图将Python扭曲成某种东西呢?我通常会尝试以这种方式编程,无论是命令行(shell管道),C#,Ruby还是Java(比Python更痛苦)。我应该停止戳它疼吗?

2 个答案:

答案 0 :(得分:4)

您可以使用生成器生成bar值;你有一个不需要的发电机级别:

[bar for bar in (x.bar for x in somelist if x.foo > 10) if bar.baz.startswith('/')]

您可以先将嵌套生成器分配给变量:

bars = (x.bar for x in somelist if x.foo > 10)
[bar for bar in bars if bar.baz.startswith('/')]

如果您想将事物保持在行长度限制内。生成器只会被使用一次,仅为.bar的每个元素访问一次昂贵的somelist属性。

如果要复制C#和Ruby代码的读取顺序,可以使用单独的生成器来执行此操作:

filtered_on_foo = (x for x in somelist if x.foo > 10)
bar_selected = (x.bar for x in filtered_on_foo)
filtered_on_baz = [bar for bar in bar_selected if bar.baz.startswith('/')]

但现在通过单独选择会产生额外的循环。

答案 1 :(得分:1)

其实我是C#开发人员,我非常喜欢LINQ(不像Python那么多:),我一直想知道为什么没有Python版的LINQ。

但是我没有时间正确检查这个,因为我只是为了好玩而使用Python。 因此,在我开始搜索之后,我会发现Python中存在任何类似LINQ的东西(如果不存在这样的模块,我实际上正在考虑自己写这样的东西)。

我觉得这个很好 - A Python implementation of LINQ to objects and Parallel LINQ to objects (ASQ)

对于你的情况,它可以像这样工作:

from asq.initiators import query

a = [{"foo":1, "bar": {"baz":"aaaa"}}, {"foo": 11, "bar": {"baz":"/ddddd"}}]

q = query(a).where(lambda x: x["foo"] > 10).select(lambda x: x["bar"]).where(lambda x: x['baz'].startswith('/'))

q.to_list()
# gives [{'foo': 11, 'bar': {'baz': '/ddddd'}}]

我发现唯一的缺点就是不可能像这样格式化这个查询:

q = query(a).where(lambda x: x["foo"] > 10)
            .select(lambda x: x["bar"])
            .where(lambda x: x['baz'].startswith('/'))

您也可以在功能样式中执行此处理:

q = ifilter(lambda x: x["foo"] > 10, a)
q = imap(lambda x: x["bar"], q)
q = ifilter(lambda x: x["baz"].startswith('/'), q)