Pythonのクロージャ、nonlocalの使い方
 Author: 水卜

クロージャとは

def counter():
    cnt = [0]
    print("counter")

    def inner():
        cnt[0] += 1
        print(f"countup {cnt}")
    return inner
  
counter = counter()
# counter
counter()
# countup [1]
counter()
# countup [2]

この例ではcounter()にカウントアップするcnt変数を定義し、その中で定義されたinner()でカウントアップしている。

このようにネストされた関数のことをクロージャと呼ぶ。

inner()がクロージャ、counter()がエンクロージャ。

Pythonでは外の関数のスコープの変数に代入することはできない。

代入はできないが、ミュータブルオブジェクトの書き換えはできるため、cntはここではList型としている。

クロージャの性質

クロージャは外側のスコープの変数を記憶しておくことができる。

エンクロージャを変数に格納すれば、cntはクロージャが記憶しているため、値を保持したままカウントアップできる。

counter = counter()
# counter
counter()
# countup [1]
counter()
# countup [2]

nonlocal

先ほどはクロージャからスコープ外で定義された変数に代入できず、スコープ外の変数をイミュータブルなList型にすることで書き換えを可能にした。

この場合nonlocalを使うとより簡潔に解決できる。

def counter():
    cnt = 0
    print("counter")

    def inner():
      	nonlocal cnt
        cnt += 1
        print(f"countup {cnt}")
    return inner
  
counter = counter()
# counter
counter()
# countup 1
counter()
# countup 2

nonlocalで宣言されたスコープ外の変数は、クロージャの中でも代入が可能である。

global

globalという宣言の仕方もある。

globalの場合、global変数をクロージャの中で扱うことができる。

cnt = 0
def counter():
    print("counter")

    def inner():
      	global cnt
        cnt += 1
        print(f"countup {cnt}")
    return inner