繁体   English   中英

Python中链表表达式的正确样式

[英]Correct style for chained list expressions in Python

我正在尝试对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痛苦得多)。 我应该在痛苦的地方停止戳戳吗?

您可以使用生成器来生成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('/')]

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

如果要复制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('/')]

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

实际上,我以C#开发人员的身份工作,而且我非常喜欢LINQ(虽然不像Python那样多:)),我一直想知道为什么没有Python版本的LINQ。

但是我从来没有时间去正确地检查它,因为我只是为了好玩而使用Python。 因此,在您提出问题之后,我开始搜索是否有类似Python的LINQ存在(如果没有这样的模块,我实际上是在考虑自己编写类似的东西)。

我认为这很好- 一种对对象的LINQ和对对象的并行LINQ(ASQ)的Python实现

对于您来说,它可以像这样工作:

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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM