Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

About python closure

Below is an example I got from someone's blog about python closure. I run it in python 2.7 and get a output different from my expect.

flist = []

for i in xrange(3):
    def func(x):
        return x*i
    flist.append(func)

for f in flist:
    print f(2)

My expected output is: 0, 2, 4
But the output is: 4, 4, 4
Is there anyone could help to explain it?
Thank you in advance.

like image 922
Alex.Zhang Avatar asked Jul 10 '12 07:07

Alex.Zhang


People also ask

Why closure is used Python?

Closures can avoid the use of global values and provides some form of data hiding. It can also provide an object oriented solution to the problem.

Which is true about closure in Python?

Python Closures are these inner functions that are enclosed within the outer function. Closures can access variables present in the outer function scope. It can access these variables even after the outer function has completed its execution.

What is the function of closure?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.

What is closure and decorators in Python?

A decorator is a function that takes in a function and returns an augmented copy of that function. When writing closures and decorators, you must keep the scope of each function in mind. In Python, functions define scope. Closures have access to the scope of the function that returns them; the decorator's scope.


3 Answers

Loops do not introduce scope in Python, so all three functions close over the same i variable, and will refer to its final value after the loop finishes, which is 2.

It seems as though nearly everyone I talk to who uses closures in Python has been bitten by this. The corollary is that the outer function can change i but the inner function cannot (since that would make i a local instead of a closure based on Python's syntactic rules).

There are two ways to address this:

# avoid closures and use default args which copy on function definition
for i in xrange(3):
    def func(x, i=i):
        return x*i
    flist.append(func)

# or introduce an extra scope to close the value you want to keep around:
for i in xrange(3):
    def makefunc(i):
        def func(x):
            return x*i
        return func
    flist.append(makefunc(i))

# the second can be simplified to use a single makefunc():
def makefunc(i):
    def func(x):
        return x*i
    return func
for i in xrange(3):
    flist.append(makefunc(i))

# if your inner function is simple enough, lambda works as well for either option:
for i in xrange(3):
    flist.append(lambda x, i=i: x*i)

def makefunc(i):
    return lambda x: x*i
for i in xrange(3):
    flist.append(makefunc(i))
like image 118
Walter Mundt Avatar answered Oct 20 '22 00:10

Walter Mundt


You are not creating closures. You are generating a list of functions which each access the global variable i which is equal to 2 after the first loop. Thus you end up with 2 * 2 for each function call.

like image 42
mhawke Avatar answered Oct 20 '22 00:10

mhawke


Each function accesses the global i.

functools.partial comes to rescue:

from functools import partial
flist = []

for i in xrange(3):
    def func(x, multiplier=None):
        return x * multiplier
    flist.append(partial(func, multiplier=i))
like image 44
Matthias Avatar answered Oct 19 '22 23:10

Matthias