Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

Python クロージャー

クロージャーの作成

最初のCODE

Jupyterlabを使っていつも遊んでいます。
今回はカウンターをクロージャーを使って作ってみようと思います。

copy

def makeCounter(a):
    b = a
    def inner():
        b += 1
        return b
    return inner

counter = makeCounter(10)

print(counter())
print(counter())
print(counter())

もののみごとに弾かれました。print(counter())がエラーとなってますので、counter()としてみましたが変わりませんでした。b += 1 にもエラーが出ているので b = 1 として、変数 b の ID を見てみることにしました。

Pythonのすべてのオブジェクトには独自の一意のIDがあり、id()関数は指定されたオブジェクトのIDを返します。
IDはオブジェクトのアドレスでオブジェクトの作成時に割り当てられ、プログラムを実行するたびごとに異なります。

点検に使ったCODE

copy

def makeCounter(a):
    b = a
    print(id(b))
    def inner():
        b = 1
        print(id(b))
        return b
    return inner

counter = makeCounter(10)

counter()
counter()
counter()

外部関数の変数 b と内部関数の中の変数 b が違っていることが分かりました。これでは、内部関数の中の変数 b が初期化されていないため加算もできません。

解決したCODE

copy

def makeCounter(a):
    b = [a]
    print(id(b))
    def inner():
        b[0] += 1
        print(id(b))
        print(b[0])
        return b[0]
    return inner

counter = makeCounter(10)

counter()
counter()
counter()

変数 b を参照系のリストにしてみました。外部関数・内部関数の変数 b のIDは一致して counter を呼び出す度に 1 づつ加算されて目的が果たされています。

最終形

不必要なところは取り除いて最終形はこのようになりました。

copy

def makeCounter(a):
    b = [a]
    def inner():
        b[0] += 1
        return b[0]
    return inner

counter = makeCounter(10)

print(counter())
print(counter())
print(counter())

考察

copy

class Count:
    def __init__(self, realpart):
        self.r = realpart

def makeCounter(a):
    nextval = Count(a)
    def inner():
        nextval.r += 1
        return nextval.r
    return inner

counter = makeCounter(10)

print(counter())
print(counter())
print(counter())

変数 b が参照系ならよさそうなので他にどのような型できるのか考察してみました。
出てきた考えが class を使ってみることで、実行してみました。うまく機能しているようです。