Python Tips: Python 3 の nonlocal を使いたい

タイトルそのままですが、 Python 3 の nonlocal の利用方法について説明してみたいと思います。

Python の変数スコープは原則「関数」がスコープの切れ目となっており、関数の内部で定義された変数に関数の外部からアクセスすることはできません。

def myfunc():
    a = 10

print(a)  # => NameError: name 'a' is not defined

一方、関数の内側から関数の外側にある変数にはアクセスすることが可能です。

a = 10

def myfunc():
    print(a)

myfunc()  # => 10

ただし、関数の内側から外側の変数へのアクセスは基本的に「参照」のみが可能です。値を更新するには nonlocal 宣言をしなくてはなりません。

nonlocal を使った例はこちら。

def gen_counter():
    """呼び出すごとにカウントを上げるカウンタを生成する"""

    # クロージャ _counter で利用する現在のカウント
    count = 0

    def _counter(reset=False):
          # 関数の外にある count を更新したいので nonlocal 宣言をする
        nonlocal count

        if reset:
            count = 0
        count += 1
        return count

    return _counter

# カウンタを使用する
c1 = gen_counter()

# nonlocal のおかげで更新した count の値が保持できることの確認
print(c1())  # => 1
print(c1())  # => 2
print(c1())  # => 3

print(c1(reset=True))  # => 1
print(c1())  # => 2

nonlocal 宣言をすることで、 _counter() 関数の外側にある count 変数を _counter() の内側で更新することができています。

この例では nonlocal 宣言をしないとむしろ「 UnboundLocalError: local variable 'count' referenced before assignment 」というエラーが出てしまうため実行すること自体できません。

ただし、関数の内側から外側の変数の「代入」はできないということについては 1 点注意が必要です。ミュータブル( mutable )なデータ型の場合は「代入」はできなくてもその中身を変更することはできてしまいます。ミュータブルなデータ型の代表はリストです。

def gen_stack():
    """スタックを生成する"""
    stack = []

    def _stack(value=False):
        if value:
            # 変数 stack は関数の外側にあるが変更が可能
            stack.append(value)
        else:
            # 変数 stack は関数の外側にあるが変更が可能
            return stack.pop()

    return _stack

q = gen_stack()

# スタックに値を入れる
q(3)  # stack: [3]
q(5)  # stack: [3, 5]
q(7)  # stack: [3, 5, 7]

# スタックから値を取り出す
print(q())  # => 7  stack: [3, 5]
print(q())  # => 5  stack: [3]
print(q())  # => 3  stack: []

以上です。

ロジックが複雑になる場合はムリに関数と nonlocal を使うのではなく、クラスを定義してアトリビュートを使う形にするのが一般的かと思います。そのため、このあたりで大きくハマることはあまり無いかと思いますが、利用する場合には挙動を正しく理解して使わないと思わぬバグが生まれるので注意が必要ですね。