Local variables in nested functions

蓝咒 提交于 2019-11-25 22:14:41

问题


Okay, bear with me on this, I know it\'s going to look horribly convoluted, but please help me understand what\'s happening.

from functools import partial

class Cage(object):
    def __init__(self, animal):
        self.animal = animal

def gotimes(do_the_petting):
    do_the_petting()

def get_petters():
    for animal in [\'cow\', \'dog\', \'cat\']:
        cage = Cage(animal)

        def pet_function():
            print \"Mary pets the \" + cage.animal + \".\"

        yield (animal, partial(gotimes, pet_function))

funs = list(get_petters())

for name, f in funs:
    print name + \":\", 
    f()

Gives:

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

So basically, why am I not getting three different animals? Isn\'t the cage \'packaged\' into the local scope of the nested function? If not, how does a call to the nested function look up the local variables?

I know that running into these kind of problems usually means one is \'doing it wrong\', but I\'d like to understand what happens.


回答1:


The nested function looks up variables from the parent scope when executed, not when defined.

The function body is compiled, and the 'free' variables (not defined in the function itself by assignment), are verified, then bound as closure cells to the function, with the code using an index to reference each cell. pet_function thus has one free variable (cage) which is then referenced via a closure cell, index 0. The closure itself points to the local variable cage in the get_petters function.

When you actually call the function, that closure is then used to look at the value of cage in the surrounding scope at the time you call the function. Here lies the problem. By the time you call your functions, the get_petters function is already done computing it's results. The cage local variable at some point during that execution was assigned each of the 'cow', 'dog', and 'cat' strings, but at the end of the function, cage contains that last value 'cat'. Thus, when you call each of the dynamically returned functions, you get the value 'cat' printed.

The work-around is to not rely on closures. You can use a partial function instead, create a new function scope, or bind the variable as a default value for a keyword parameter.

  • Partial function example, using functools.partial():

    from functools import partial
    
    def pet_function(cage=None):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage)))
    
  • Creating a new scope example:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    
  • Binding the variable as a default value for a keyword parameter:

    def pet_function(cage=cage):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, pet_function))
    

There is no need to define the scoped_cage function in the loop, compilation only takes place once, not on each iteration of the loop.




回答2:


My understanding is that cage is looked for in the parent function namespace when the yielded pet_function is actually called, not before.

So when you do

funs = list(get_petters())

You generate 3 functions which will find the lastly created cage.

If you replace your last loop with :

for name, f in get_petters():
    print name + ":", 
    f()

You will actually get :

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.



回答3:


This stems from the following

for i in range(2): 
    pass

print i is 1

after iterating the value of i is lazily stored as its final value.

As a generator the function would work (i.e. printing each value in turn), but when transforming to a list it runs over the generator, hence all calls to cage (cage.animal) return cats.



来源:https://stackoverflow.com/questions/12423614/local-variables-in-nested-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!