Scope of Python closures and lambdas

lambda writing

def fun():
    for i in range(3):
        yield lambda x : x * i
 
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))

How to write closures

def fun():
    result = []
    for i in range(3):
        def demo(x):
            return x * i
        result.append(demo)
    return result
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))

The results of the above two ways of writing are 2, 4, and 6. According to the original idea, the results should be 0, 2, and 6.

problem causes:

The problem lies in python’s variable lookup rules, LEGB (local, enclusing, global, bulitin), in the above example, i is in the closure scope (enclusing), while Python’s closure is late binding, in the closure The value of the variable i used is found when the inner function is called.

Solution

Turn closure scope into local scope

lambda writing

def fun():
    for i in range(3):
        yield lambda x, i = i: x * i
 
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))

Closure writing

def fun():
    result = []
    for i in range(3):
        def demo(x, i=i):
            return x * i
        result.append(demo)
    return result
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))

The above output results 0, 2, 6

another situation:

def fun():
    for i in range(3):
        yield lambda x : x * i
f0, f1, f2 = fun()
print(f0(1), f1(2), f2(3))

The output is still 2, 4, 6

problem causes

The generator (or iterator) returned by the fun() method does not actually execute, but executes each time it is called.

When printing after traversal, the i variable uses the value of the last call. If you think of lambda as a closure method, the value of the variable i is still the closure scope (no local)

Pit in python (closures and lambdas)

def create():
    return [lambda x:i*x for i in range(5)]
  
for i in create():
    print(i(2))

result:

8
8
8
8
8

The return value of the create function is a list, and each element of the list is a function — a function that multiplies the input parameter x by a multiple of i. The expected result is 0, 2, 4, 6, 8. But the result is 5 8s, which is not unexpected.

Since lambdas are often used when this trap occurs, it may be considered a problem with lambdas, but lambdas say they are not willing to take the blame. The essence of the problem lies in the attribute lookup rules in python, LEGB (local, enclusing, global, bulitin), in the above example, i is in the closure scope (enclusing), and Python’s closure is late binding, This means that the value of the variable used in the closure is queried when the inner function is called

The solution is also very simple, that is to change the closure scope to a local scope.

def create():
    return [lambda x, i=i:i*x for i in range(5)]
  
for i in create():
    print(i(2))

To write it another way:

def create():
    a = []
    for i in range(5):
        def demo(x, i=i):
            return x*i
        a.append(demo)
    return a
  
for i in create():
    print(i(2))

The above two spellings are the same

result:

0
2
4
6
8

Another example with a similar problem

nums = range(2,20)
for i in nums:
    nums = filter(lambda x: x==i or x%i, nums)
print(list(nums))

result:

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

Also according to the normal logic, the result should be:

[2, 3, 5, 7, 11, 13, 17, 19]

The cause of the problem:

In python3, the filter() function returns an iterator, so it does not actually execute it, but executes it every time it is called (the list of values returned by filter() in python2 does not have this phenomenon)
When printing is executed after traversal, the function in the loop is now executed. Same as the problem with the above chestnut, the i variable uses the value of the last call. The difference from the above chestnut is that the above chestnut uses the value of the embedded scope. , and this chestnut uses the value of the global i

Modify the code:

nums = range(2,20)
for i in nums:
    nums = filter(lambda x,i=i: x==i or x%i, nums)
print(list(nums))

result:

[2, 3, 5, 7, 11, 13, 17, 19]

Leave a Reply

Your email address will not be published.

en_USEnglish