Is it possible to grab a return value from a Python list comprehension to use a condition?

I want to build a value in a list comprehension, but also filter that value. For example:

[expensive_function(x) for x in generator where expensive_function(x) < 5] 

I want to double call expensive_function twice per iteration.

generator can return an infinite series, and the list of concepts is not evaluated lazily. So this will not work:

 [y in [expensive_function(x) for x in generator where expensive_function(x)] where y < 5] 

I could write it in another way, but it feels good to understand the list, and I'm sure this is a common usage pattern (maybe or not!).

+7
source share
4 answers

If the generator can be infinite, you do not want to use list comprehension. And not everything should be single-line.

 def filtered_gen(gen): for item in gen: result = expensive_function(item) if result < 5: yield result 
+10
source

you should make 2 generator expressions:

 ys_all = (expensive(x) for x in xs) ys_filtered = (y for y in ys_all if y <5) 

or

 from itertools import imap, ifilter ys = ifilter(lambda y : y < 5, imap(expensive, xs)) 
+2
source

I am going to answer part of the question about how to capture intermediate results in understanding a list for use in a state and ignore the question of understanding a list built from an infinite generator (which is obviously not going to work), just in case someone looking for the answer to the question in the title, comes here.

So, you have a list comprehension as follows:

 [expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0] 

And you want to avoid calculating expensive_function twice when it passes your filter. Languages ​​with a more expressive syntax of understanding (Scala, Haskell, etc.) Allow you to simply assign names to expressions calculated from understanding variables, which allows you to do the following things:

 # NOT REAL PYTHON [result for x in xrange(5) for result = expensive_function(x) if result % 2 == 0] 

But you can easily emulate this by turning the assignment result = expensive_function(x) into another iteration of for over a sequence of one element:

 [result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0] 

And the proof:

 >>> def expensive_function(x): print 'expensive_function({})'.format(x) return x + 10 >>> [expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0] expensive_function(0) expensive_function(0) expensive_function(1) expensive_function(2) expensive_function(2) expensive_function(3) expensive_function(4) expensive_function(4) [10, 12, 14] >>> [result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0] expensive_function(0) expensive_function(1) expensive_function(2) expensive_function(3) expensive_function(4) [10, 12, 14] 
+2
source

Warning This is a bit confusing, but does the job. I will use an example to explain this.

Say expensive_function = math.sin

infinite generator = collections.count(0.1,0.1)

then

 [z for z in (y if y < 5 else next(iter([])) for y in (math.sin(x) for x in itertools.count(0.1,0.1)))] 

is an

 [0.09983341664682815, 0.19866933079506122, 0.2955202066613396, 0.3894183423086505, 0.479425538604203] 

So your problem comes down to

 [z for z in (y if y < 0.5 else next(iter([])) \ for y in (expensive_function(x) for x in generator))] 

The trick is to force a StopIteration from the generator and nothing more elegant than next(iter([]))

Here expensive_function is called only once per iteration.

Extend an infinite generator using a finite generator with a stop condition. Since the generator will not allow raise StopIteration , we choose a minimized method, i.e. next(iter([])) And now you have a finite generator that you can use in understanding List

Since the OP was associated with the application of the above method for the monotonic function, a fictitious nonmonotonic function is given here.

f(x) = random.randint(1,100)*x function f(x) = random.randint(1,100)*x

Stop condition = < 7

 [z for z in (y if y < 7 else next(iter([])) for y in (random.randint(1,10)*x for x in itertools.count(0.1,0.1)))] [0.9, 0.6000000000000001, 1.8000000000000003, 4.0, 0.5, 6.0, 4.8999999999999995, 3.1999999999999997, 3.5999999999999996, 5.999999999999999] 

Btw: sin in the true sense is nonmonotonic over the entire range (0,2pi)

+1
source

All Articles