
Andrey Popp wrote:
I think that producing a list of tuples (that is conceptually image of mapping from some_iterable set) is basic operation. But... ok, now we have three ways to produce list below:
[(f(x), f(x)) for x in some_iterable if f(x) < 2]
Taking in isolation, there is no reason to produce a list rather than an iterator.
1)
def g(iterable): for x in iterable: y = f(x) if y < 2: yield (y, y)
This does more than the above because g is reusable both with the same iterable and other iterables.
2)
[(y, y) for y in (f(x) for x in some_iterable) if y < 2]
Though I would probably write the reusable generator I might write this as ygen = (f(x) for x in some_iterable) # or map(f, some_iterable) if f is an existing function ypairs = ((y, y) for y in ygen if y < 2) There are really two ideas: map f to some_iterable make pairs conditionally. There should be no shame in putting each in a separate statement. Nested generators do not really turn this into 'two' iterations, as iterating with ypairs will run the implied loop in synchrony.
3)
map(lambda obj: (obj, obj), filter(lambda y: y < 2, map(f, some_iterable)))
And none of them does not look as obvious as
[(f(x), f(x)) for x in some_iterable if f(x) < 2]
, doesn't it? While proposed variant with where-clause
[(y, y) for x in some_iterable if y < 2 where y = f(x)]
looks more naturally than three suggested variants.
'natural' is in the eye of the beholder
I give strong emphasis on that fact, that where-clause is only syntactic sugar, suggested for better readability.
Too much sugar = stomach ache ;-). Terry Jan Reedy