1

When I change to use lambda in filter(), the result of code changes. Why?

Code to calculate primes. You can copy the code to run it directly.

def get_odd_iter():
    oddNum = 1
    while True:
        oddNum = oddNum + 2
        yield oddNum

def not_divisible(n):
    return lambda x: x%n>0


def prime_iter():
    yield 2
    odd_iter = get_odd_iter()

    while True: 
        odd = next(odd_iter)
        yield odd
        # odd_iter = filter(not_divisible(odd), odd_iter)  # <--(1)
        odd_iter = filter((lambda x: x%odd>0) , odd_iter)  # <--(2)


p = prime_iter()

print(next(p))
print(next(p))
print(next(p))
print(next(p))
print(next(p))
print(next(p))

When I use (1), everything goes well. When I change to (2), 9 will be in the result, which is actually not a prime number.

2 Answers2

1

You're nesting filters, and with the lambda, all of them are closures on odd in the calling scope, and they're all lazy; none of them produce values until requested. So when you reassign odd in the next run of the while loop, they all use the new value of odd, not the individual values of odd from when each lambda/filter wrapping was created.

Using the function, they each close on the odd in function scope, which never changes. To achieve a similar effect with lambdas, capture the present value of odd as a default argument:

    odd_iter = filter((lambda x, odd=odd: x%odd>0), odd_iter)
                                 ^^^^^^^ captured

which means that copy of the lambda is no longer referencing closure scope to find the updating value of odd, it just reads it once for the current value of odd and then uses it's own local, unchanging cached value.

ShadowRanger
  • 108,619
  • 9
  • 124
  • 184
  • Hi, thanks for your response, very useful. "none of them produce values until requested", "requested" refers to "yield"? – Jeffrey Hao Sep 09 '19 at 10:58
  • @JeffreyHao: Roughly; `filter` is implemented in C and doesn't actually use the `yield` keyword, but close enough. Point is, none of them are produced until you actually iterate the result, and each result is only produced at the moment you reach it. So each `next` pulls a value, using the current value of `odd` for all `filter`s. – ShadowRanger Sep 09 '19 at 17:29
0

This is because lambdas look up their arguments' names at the time of calling, not at the time of creation. The first variant works because you are binding the value of odd to the lambda in not_divisible. Consider changing

odd_iter = filter(not_divisible(odd), odd_iter)

to

odd_iter = filter(not_divisible, odd_iter)

just leaving the callable without arguments, and you will be getting similar incorrect results.

For the lambda to work, you must explicitly set (bind) its 'hidden' argument, like so:

odd_iter = filter(lambda x,odd=odd: x%odd>0, odd_iter)

This will produce correct results.

Also, consider a better solution to generate primes, as discussed here for instance.

s0mbre
  • 177
  • 6