その時々

その時々で違うんです。特に決まっていないんです。

pythonのクロージャについての勉強

pythonを勉強しているのですが、クロージャというものがいまいちよくわかりません。
よくわからない状態で書いているので取り留めもないメモです。

そもそもクロージャとは何かをwikiで調べると

クロージャクロージャー、closure、閉包)はプログラミング言語における関数の一種。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。

wikipediaより

とのこと。
いまいちよくわかりません。

pythonのねずみ本を見ると

すでに存在しない外側のスコープに属する変数を保持しているオブジェクトのこと

と書いてあります。

結局なんのことか分かりません。

さらにサンプルを見ても難解です。

def f1():
   x = 88
   def f2():
       print x
   f2()

これを実行すると

>>> f1()
88

と表示されます。
f1が終了しているのにf2の中でxは保持されているのです。

さらに

def maker(N):
    def action(X):
        return X ** N
    return action

これを次のように実行してみます。

>>> f = maker(2)
>>> f(3)
9

3 ** 2で9が返ってきて
内側のスコープのactionに2が渡されています。

別の変数にmakerをセットして

>>> g = maker(4)
>>> f(2)
16

2 ** 4で16が返ってくるわけですが、さっきの変数fは残っていて

>>> f(5)
25

5 ** 2で25が返ってくるわけです。

さらにこの内側のスコープに渡すのはlambda式にも使えるようで

def func():
    x = 4
    return lambda n: x ** n

とすると

>>> x = func()
>>> x(2)
16

となり
4 ** 2で16が返ってきます。

こうやって見てみるとクロージャとは内側のスコープに外側で作った変数を引き渡すことのように思えてきます。

もう一度wikiの説明を見てみますと

クロージャクロージャー、closure、閉包)はプログラミング言語における関数の一種。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。

wikipediaより

なんとなく見えてきました。
静的スコープ、すなわち動的スコープではないときに、引数を使わずに外側のスコープの変数と関数の中で定義した関数をひとまとめにして保持するような仕組みなんですかね。

ちょっとサンプルを追加してみます。

よく使われているカウントアップのサンプルです。

def counter():
    x = [0]
    def up():
            print x[0]
            x[0] += 1
    return up

この関数は呼び出されるごとにカウントアップします。

>>> action()
0
>>> action()
1
>>> action()
2
>>> action()
3

ここでx = [0]としているのはx = 0だとxは不変性のオブジェクトのため可変性のオブジェクトであるリストとしているのです。
外側のスコープの変数には代入できないためです。
python3.0からはnonlocalを使えば解決できるようですが、python2.5や2.6ではこのようなやりかたをするようです。

もう一個オリジナルのサンプルを作ってみました。

def toggle():
    sw = [True]
    def onoff():
            print sw[0]
            sw[0] = not sw[0]
    return onoff

関数が呼ばれるたびにスイッチがONOFFします。

>>> action()
True
>>> action()
False
>>> action()
True
>>> action()
False


なんとなく分かってきたのかもしれません。