one liner to go through an iterable (generator)

Viewed 107

I encountered some code looking like:

[func(val) for val in iterable]

There is an iterable (in my case a generator) for which a user wants to call a function for each value for its side effects (func could for example just be print) but where the return value is irrelevant.

What I don't like about this approach is, that a temporary list is created, which might consume quite some memory if the generator yields a lot of values.

If the return value of func always evaluates to False, then following works:

any(func(val) for val in iterable)

If the return value of func always evaluates to True, then following works:

all(func(val) for val in iterable)

What would I have to do if the return value of func can evaluate to True or to False

Anything better then forcing the value to False?

The best I came up with is:

any(func(val) and False for val in iterable)

or

all(func(val) or True for val in iterable)
3 Answers

Probably just

for val in iterable:
   func(val)

is clearest.

for val in iterable: func(val)

is available if a one-liner is truly necessary.

How about using a set with bool function?

{bool(func(val)) for val in iterable}

EDITED:
After looking at @gelonida's analysis, I believe the following is a bit faster.

{None for val in iterable(N) if func(val)}

Just a synthesis and timing analysis of the given answers / potential solutions

It seems that @LeopardShark's answer is the shortest, most readable answer. and amongst the fastest. (timeit is not exact and I didn't look at the byte codes)

Speed wise - @LeopardShark's answer - @ywbaek's second suggestion - The initial code, that I found if N is not too big (~10000) - the suggestions, that I posted in my question

The initial code has the drawback of allocating releasing memory for nothing.

The code I suggested in my question has the drawback to be less intuitive to understand and if messing up the combination of (all, any) and (and False, or True) one might not execute everything as expected and is also a little less performant

@ywbaek's solution is safer than my suggestions and about as intuitive, but is executing a little faster.

The simplest solution has the minor drawback of not being usable as lambda.

My code for timing:

N=10000
M=500

called = 0
def func(v):
    global called
    called += 1
    v * v * v * v * v *v / (v+0.1)

def iterable(N):
    for v in range(N):
        v * 2
        yield v

def testrun():
    global called
    called=0
    print(timeit(test, number=M), end=" ")
    print(called)

print("consume some CPU")
timeit(lambda: 3**.5 **.2) # it seems, that timeit is a little more predictable if I let the process warm up a little

print("Start measures")

def test():
    for val in iterable(N): func(val)
testrun()

def test():
    {None for val in iterable(N) if func(val)}
testrun()

def test():
    [func(val) for val in iterable(N)]
testrun()

def test():
    all(func(val) or True for val in iterable(N))
testrun()

def test():
    any(func(val) and False for val in iterable(N))
testrun()

results on my old PC:

consume some CPU
Start measures
3.864932143012993 5000000
3.916696268017404 5000000
4.0817033689818345 5000000
4.293206526956055 5000000
4.319622751965653 5000000

Related