Why can't i replace the function name by it's return value in this example?

Viewed 80

I'm writing a python program to print primes. Here is my code and some comments

# make an odd iterator
def _odd_iter():
    n = 3
    while True:
        yield n
        n = n + 2

# return a lambda funcion to find which number couldn't be divisible by p
def _not_divisible(p):
    return lambda x : x % p > 0

def primes():
    yield 2
    it = _odd_iter() # initial sequence
    while True:
        n = next(it) 
        yield n  # give the first element in the new iterator it
        # When I use code below, filter seems doesn't work well. 
        it = filter(lambda x : x % n > 0, it) 
        # When I use code below, filter works well
        # it = filter(_not_divisible(n), it) 

# print the first 10 primes
prime = primes()
for i in range(10):
    print(next(prime), end=', ')

My main question is in the line with filter. When I use the function _not_divisible(n) in the filter, I got the output:

2, 3, 5, 7, 11, 13, 17, 19, 23, 29

It's what I want. However when I use function _not_divisible(n)'s return value lambda x: x % n > 0 instead of the function itself, I got the output:

2, 3, 5, 7, 9, 11, 13, 15, 17, 19

It seems that the filter doesn't work.

I also test if _not_divisible(3) and lambda x: x % 3 > 0 are same:

# test if _not_divisible(n) and lambda x:x % n > 0 are same, This works fine. 
ita = filter(lambda x:x % 3 > 0, range(20))
itb = filter(_not_divisible(3), range(20))
while True:
    try:
        print(next(ita), end=', ')
        print(next(itb), end=', ')
    except StopIteration:
        break 

It gave me a good output:

1, 1, 2, 2, 4, 4, 5, 5, 7, 7, 8, 8, 10, 10, 11, 11, 13, 13, 14, 14, 16, 16, 17, 17, 19, 
19,

So what caused the problem, anyone can help?

1 Answers

It's a sneaky one. The problem is with n, which you hardcoded in your last test.

ita = filter(lambda x:x % 3 > 0, range(20))
itb = filter(_not_divisible(3), range(20))

Let's modify the code and see.

ita = []
itb = []
for n in range(3, 8, 2):
    ita.append(filter(lambda x:x % n > 0, range(20)))
    itb.append(filter(_not_divisible(n), range(20)))

for a, b in zip(ita, itb):
    print(list(a))
    print(list(b))
    print()

This prints out

[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19]
[1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19]

[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19]
[1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19]

[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19]
[1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19]

The lambda will always look up the value of n when it is called. By the time you call all lambdas in ita, they will all see the value n had at the end of the iteration, which is 7.

In contrast, when you pass that value to a function and call the lambda from within, you end up "freezing" the value. Because the function has its own local context and n is whatever value it was passed during the call. So each lambda in itb will look up n from a different context each time.

Related