🙌

【Python】nonlocal宣言とは何か?

に公開

今回はPythonプログラミングで使われる「nonlocal宣言」について説明します。

ぼくが初めてこの機能を知ったときは「なんだこれ?」と思いましたが、実はとても便利な機能です。

nonlocal宣言とは?

Pythonでは、関数の中に関数を作ることができます。

これを「ネスト(入れ子)された関数」と呼びます。

そのとき、内側の関数から外側の関数で定義された変数を変更したいことがあります。

その時に使うのが「nonlocal宣言」です。

nonlocal宣言を使うと、内側の関数から外側の関数の変数を参照するだけでなく、変更することもできるようになります。

なぜnonlocal宣言が必要なのか?

実際のコード例で見てみましょう。

以下のコードは、nonlocal宣言を使わない場合です:

def outer_function():
    count = 0
    
    def inner_function():
        count = count + 1  # エラー発生!
        print(f"カウント:{count}")
    
    inner_function()
    
outer_function()

このコードを実行すると、エラーが発生します

なぜなら、Python は inner_function の中の count を新しいローカル変数だと判断するからです。

でも、実は外側の count を使いたかったんです。

エラーメッセージは次のようになります:

UnboundLocalError: local variable 'count' referenced before assignment

これを解決するために、nonlocal宣言を使います。

nonlocal宣言の使い方

nonlocal宣言の使い方はとても簡単です。

内側の関数の先頭に nonlocal 変数名 と書くだけ。

先ほどのコードを修正してみましょう:

def outer_function():
    count = 0
    
    def inner_function():
        nonlocal count  # これがnonlocal宣言です!
        count = count + 1  # これでエラーにならなくなります
        print(f"カウント:{count}")
    
    inner_function()
    print(f"外側の関数でのカウント:{count}")
    
outer_function()

実行結果:

カウント:1
外側の関数でのカウント:1

このように、nonlocal宣言を使うことで、内側の関数から外側の関数の変数を変更できるようになりました。

そして、変更は外側の関数にも反映されます。

nonlocal宣言の実用例

1. カウンターを作る

nonlocalを使ったも実用的な例として、関数を呼び出すたびにカウントアップする関数を作ってみましょう。(先ほどの例とほぼ同じです)

def make_counter():
    count = 0
    
    def counter():
        nonlocal count
        count += 1
        return count
    
    return counter

my_counter = make_counter()
print(my_counter())  # 出力: 1
print(my_counter())  # 出力: 2
print(my_counter())  # 出力: 3

この例では、make_counter関数はcounter関数を返します。

そして、返されたcounter関数を呼び出すたびに、カウントがアップしていきます。

これはクロージャという技術を使っていて、nonlocal宣言がその重要な役割を果たしています。

クロージャという技術

クロージャとは、関数と、その関数が参照している周囲の変数の環境をセットにしたものです。

つまり、ある関数が自分の外側で定義された変数を「記憶」して使える仕組みのこと。

Pythonでクロージャを作るとき、以下のような特徴があります:

  1. 関数の中で関数を定義する:外側の関数(外部関数)の中で内側の関数(内部関数)を定義します。
  2. 内部関数が外部関数のローカル変数を参照する:内部関数が外部関数で定義された変数を使用します。
  3. 外部関数が内部関数を返す:外部関数の戻り値として内部関数を返します。

例えば、先ほどの記事で紹介したカウンターの例はクロージャの典型的な例です:

def make_counter():
    count = 0  # 外部関数のローカル変数
    
    def counter():  # 内部関数
        nonlocal count
        count += 1
        return count
    
    return counter  # 内部関数を返す

my_counter = make_counter()
print(my_counter())  # 出力: 1
print(my_counter())  # 出力: 2

この例では:

  • make_counter関数内でcount変数とcounter関数を定義しています
  • counter関数はcount変数を使用しています
  • make_countercounter関数を返しています

my_counter = make_counter()を実行すると、my_countercounter関数が代入されますが、このとき重要なのは、counter関数だけでなく、count変数の状態も一緒に「閉じ込められる(クローズする)」ということです。

それがクロージャの名前の由来です。

クロージャの利点:

  1. 状態の保持:関数呼び出し間で状態(変数の値)を保持できます
  2. データの隠蔽:外部からは直接アクセスできない変数を持てます
  3. コードの整理:関連する関数と変数をまとめられます

クロージャは特に以下のような場面で役立ちます:

  • カウンターの実装
  • コールバック関数の作成
  • イベントハンドラ
  • データキャッシュ
  • 部分適用
  • カリー化

Pythonでクロージャを使う際は、nonlocal宣言が重要な役割を果たします。

これがないと、内部関数から外部関数の変数を変更することができません。

2. 情報を記憶する計算機

もう一つの例として、前回の計算結果を覚えておく計算機を作ってみましょう。

def create_calculator():
    last_result = 0
    
    def add(n):
        nonlocal last_result
        last_result += n
        return last_result
    
    def subtract(n):
        nonlocal last_result
        last_result -= n
        return last_result
    
    def get_result():
        return last_result
    
    return {
        "add": add,
        "subtract": subtract,
        "get_result": get_result
    }

calc = create_calculator()
print(calc["add"](5))        # 出力: 5
print(calc["subtract"](2))   # 出力: 3
print(calc["add"](10))       # 出力: 13
print(calc["get_result"]())  # 出力: 13

この計算機は、足し算や引き算をするたびに結果を記憶していて、次の計算に使います。

これも、nonlocal宣言のおかげで実現できています。

ちなみに、上記のcreate_calculator()内のreturnで、add()ではなく、addが使われている理由を説明します。

add と add() の違いは、関数オブジェクトそのものを渡すか、関数を実行した結果を渡すかという点です。

add()だと、add関数をその場で実行し、その戻り値を辞書に格納することになってしまいます。

nonlocal宣言とglobal宣言の違い

Pythonには「global宣言」もあります。

これは、関数内からグローバル変数(関数の外で定義された変数)を変更するときに使います。

nonlocal宣言とglobal宣言の違いを見てみましょう:

global_var = 0

def outer_function():
    outer_var = 1
    
    def inner_function():
        local_var = 2
        nonlocal outer_var  # 外側の関数の変数を変更
        global global_var   # グローバル変数を変更
        
        outer_var = 10
        global_var = 20
        local_var = 30
    
    inner_function()
    print(f"outer_var: {outer_var}")  # 出力: outer_var: 10

outer_function()
print(f"global_var: {global_var}")  # 出力: global_var: 20

このように、nonlocal宣言は一つ外側の関数の変数を変更するためのもので、global宣言はグローバルスコープの変数を変更するためのものです。

Discussion