Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python list comprehension with same function in guard and result

I was wondering if someone has a nice clean Pythonic and effective technique for implementing comprehensions that involve the same expression in the guard as in the result. To be clear, consider the following simple example:

def f(a):
    print "Calling", a
    return a + 1

print [ f(v) for v in xrange(3) if f(v) > 1 ]

This will print

Calling 0
Calling 1
Calling 1
Calling 2
Calling 2
[2, 3]

proving that f is called twice for most elements. This is fine and what we want if f has side-effects, but if f is some expensive operation without side-effects, the duplicate call is not desirable. But the solution that only calls f once for each element seems clumsy/verbose to me:

intermediate = ( f(v) for v in xrange(3) ) 
print [ r for r in intermediate if r > 1 ]

even if it is contracted into one line

print [ r for r in ( f(v) for v in xrange(3) ) if r > 1 ]

So, can anyone come up with something better?

like image 736
holbech Avatar asked Oct 19 '22 21:10

holbech


2 Answers

You can use the filter() function:

filter(lambda x: x > 1, [ f(v) for v in xrange(3)])

But that's about as verbose as your last suggested solution.

like image 123
user2390182 Avatar answered Oct 23 '22 09:10

user2390182


How about memoizing f, e.g.:?

def f(...): ...

def some_or_other():
    f = functools.lru_cache(1)(f)
    [ f(v) for v in xrange(3) if f(v) > 1 ]

Memoizing locally, withing the scope of your call site has the advantage that once some_or_other() returns, "memo" memory will be garbage collected, and you don't have to worry about references to v that were passed to f().

Since it's local, memo size limit 1 is sufficient.

In the simple case, you can also memoize f globally:

@functools.lru_cache()
def f(...): ...

[ f(v) for v in xrange(3) if f(v) > 1 ]
like image 35
Dima Tisnek Avatar answered Oct 23 '22 10:10

Dima Tisnek