1. ホーム
  2. python

[解決済み] ネストされた関数のローカル変数

2022-11-18 23:24:53

質問

何が起こっているのか理解するのを助けてください。

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()

与える。

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

では基本的に、なぜ私は3種類の動物を手に入れることができないのか?それは cage はネストされた関数のローカルスコープに「パッケージ」されているのでしょうか?そうでない場合、ネストされた関数への呼び出しはどのようにローカル変数を検索するのでしょうか?

私は、この種の問題に遭遇することは、通常、1つが「間違っている」ことを意味することを知っていますが、何が起こるかを理解したいと思います。

どのように解決するのですか?

ネストされた関数は、定義時ではなく、実行時に親スコープから変数を参照します。

関数本体がコンパイルされ、「自由な」変数(代入によって関数自体で定義されていない)が検証された後、クロージャセルとして関数にバインドされ、コードは各セルを参照するためにインデックスを使用します。 pet_function このように 1 つ 自由変数 ( cage ) があり、それがクロージャのセル (インデックス 0) から参照されます。クロージャはローカル変数 cage の中にある get_petters 関数を使用します。

実際にこの関数を呼び出すと、そのクロージャの値は cage の値を周囲のスコープ の値を見るために使われます。 . ここに問題がある。関数を呼び出すまでに get_petters 関数はすでにその結果を計算し終えています。そのため cage ローカル変数は、その実行中のある時点で、それぞれの 'cow' , 'dog' そして 'cat' という文字列がありますが、関数の最後には cage にはその最後の値 'cat' . このように、動的に返される各関数を呼び出すと、値 'cat' がプリントされる。

回避策としては、クロージャに頼らないことです。この場合は 部分関数 を作成し、その代わりに 新しい関数スコープ を作成するか、あるいはその変数を キーワードパラメータのデフォルト値 .

  • 部分的な関数の例。 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)))
    
    
  • 新しいスコープの例を作成する。

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    
    
  • キーワードパラメータのデフォルト値として変数をバインドします。

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

を定義する必要はありません。 scoped_cage 関数を定義する必要はなく、コンパイルは一度だけ行われます。