Saturday, 14 September 2013

Correct style for chained list expressions in Python

Correct style for chained list expressions in Python

I'm trying to write a simple query over an array of objects in Python that
would be trivial and elegant in C# or Ruby, but I'm having a hard time
making it elegant in Python. I figure I'm doing something wrong.
In C#:
list.Where(x => x.Foo > 10).Select(x => x.Bar).Where(x =>
x.Baz.StartsWith("/"))
This will create an enumeration including list[0].Bar providing
list[0].Foo > 10 and list[0].Bar.Baz starts with '/', and so on for all
the other items in list. The data flows clearly from left to right, and
further filtering / projection / aggregation can be appended on the right.
In Ruby:
list.select { |x| x.foo > 10 }.map(&:bar).select { |x| x.baz.starts_with?
'/' }
Again, it's a fairly clear flow from left to right, and further operations
can be appended with ease.
But my attempts in Python seem backwards, inside out and generally ugly:
[x for x in (x.bar for x in (x for x in list if x.foo > 10)) if
x.baz.startswith('/')]
Now I know I can combine a map and a filter in a single step with a list
comprehension, and that the above could be rewritten as this:
[x.bar for x in list where x.foo > 10 and x.bar.baz.startswith('/')]
but that rather misses the point. For one thing, the projection x.bar may
be expensive, and I don't want to evaluate it twice; for another,
projection and filtering are only two of the potential operations I'm
applying to the stream, I could be sorting, aggregating, paginating, etc.,
and not all projections and filters need be adjacent, nor the filter
applying before the projection rather than after.
Am I trying to twist Python into something it's not? I generally try to
program in this style whenever I can, whether it's the command-line (shell
pipes), C#, Ruby, or Java (a lot more pain than Python). Should I stop
poking where it hurts?

No comments:

Post a Comment