[英]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.