python closure weird behavior

2019-07-20 06:29发布

问题:

I am trying a piece of code from the question in Lexical closures in Python

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

for f in flist:
    print f.func_closure

The output is:

None
None
None

Shouldn't it be?:

(<cell at 0x9222d94: int object at 0x8cabdbc>,)
(<cell at 0x9222d94: int object at 0x8cabdbc>,)
(<cell at 0x9222d94: int object at 0x8cabdbc>,)

I have got the above output using the following code:

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

for f in flist:
    print f.func_closure

I am using Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39).

回答1:

Closures are only introduced if there are variables to be referenced outside of the global (module) scope:

>>> def foo():
...     def bar(): pass
...     return bar
...
>>> foo().func_closure is None
True
>>> spam = 'eggs'
>>> def foo():
...     def bar(): return spam
...     return bar
...
>>> foo().func_closure is None
True

Only when the inner function refers to a variable in the surrounding scope are closures generated:

>>> def foo():
...     spam = 'eggs'
...     def bar(): return spam
...     return bar
...
>>> foo().func_closure is None
False
>>> foo().func_closure
(<cell at 0x108472718: str object at 0x108471de0>,)

Note that you actually have to refer to a variable in the surrounding scope. Simply ignoring the scope gives you None again:

>>> def foo():
...     spam = 'eggs'
...     def bar(): pass
...     return bar
...
>>> foo().func_closure is None
True

In your first example, i is a module-scope variable, only in your second example do you introduce a new scope by wrapping the code in a new function actualFact.



回答2:

The language reference specifies that func_closure is "None or a tuple of cells that contain bindings for the function’s free variables."

Now, note the difference between your two versions: in the first version i is a module-level (i.e. global) variable. The result of evaluating each of the functions is the same:

>>> [f(2) for f in flist]
[4, 4, 4]

In each function, i is not free, but refers to the global i, so no, the output should not be a list of non-zero-length tuples.

In practice, you probably don't care about the value of func_closure, unless you're doing some fairly deep magic. If you are doing something magic, note that given the specification, there seems to be no good reason why func_closure should not be an empty tuple if there are no free variables, so handle that case appropriately if you want your code to be portable between even different point-versions of python.



回答3:

A cheap way to do this without a closure

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