Python nonlocal文の使い方

この記事のポイント

Pythonのnonlocal文は、ネスト関数(入れ子の関数)の内部から、ひとつ外側にある関数の変数を書き換えるために使います。

この記事を読むと、次のようなことが身に付きます。

  • ネスト関数で外側の変数を書き換える方法がわかる
  • global文との違いや使い分けがわかる
  • クロージャやカウンター関数などでの活用例を知る

この記事を通して、nonlocal文の正しい使い方をマスターすれば、関数の状態を管理したり、より高度なプログラムを書いたりできるようになります。

目次

nonlocal文とは?

Pythonでは、関数の中に関数を定義する「入れ子(ネスト)関数」を作れます。

通常、内側の関数から外側の関数で定義した変数を読み取ることはできますが、代入することはできません。もし内側の関数で同じ名前の変数に値を代入しようとすると、Pythonは「内側の関数で新しいローカル変数を定義した」と解釈してしまい、外側の関数の変数は変更されないままです。

nonlocal文は、内側の関数で扱う変数が「今いるローカルの変数ではなく、ひとつ外側の関数の変数である」ことを明示的に宣言するためのキーワードです。

nonlocal文の特徴

  • ネスト関数から、ひとつ外側の関数の変数を書き換えられるようにする
  • global文とは異なり、一番外側ではなく、あくまで外側の関数の変数を対象とする
  • 意図しないローカル変数の作成を防ぎ、コードの動作を明確にする
  • クロージャ(関数閉包)やデコレーターといった高度な機能の実装に役立つ
  • Python 3から導入された機能である

【関連】
Pythonをもっと詳しく学ぶならpaizaラーニング

基本構文

nonlocalキーワードの基本的な使い方を見ていきましょう。構文はシンプルで、内側の関数(ネスト関数)の中で「nonlocal 変数名」のように宣言するだけです。

まず、nonlocalを使わない場合に何が起こるかを確認してみましょう。

def outer_function(): animal = "イヌ" def inner_function(): animal = "ネコ" # 新しいローカル変数を作成 print("内側の関数:", animal) inner_function() print("外側の関数:", animal) outer_function()

出力結果

内側の関数: ネコ
外側の関数: イヌ

この例では、inner_functionでanimal = "ネコ"と代入しても、外側のanimal("イヌ")は変わりません。inner_functionの中でanimalという名前の新しいローカル変数が作られた、とPythonが判断するためです。結果として、inner_functionの中(ネコ)と外(イヌ)で、変数の値が異なっています。

次に、nonlocalを使ったケースを見てみましょう。

def outer_function(): animal = "イヌ" def inner_function(): nonlocal animal # 外側の関数のanimalを使う宣言 animal = "ネコ" # 外側の関数の変数を変更 print("内側の関数:", animal) inner_function() print("外側の関数:", animal) outer_function()

出力結果

内側の関数: ネコ
外側の関数: ネコ

この例では、nonlocal animalと宣言したため、inner_function内のanimalは「外側の関数のanimal」を指すようになります。

animal = "ネコ"という代入が外側の変数を直接書き換えるため、outer_functionの最後のprint文でも"ネコ"が出力されました。nonlocalは状態を保持したい場合や、関数間でデータを共有したい場合に特に役立ちます。

実用例

nonlocal文の実際の実用例を詳しく見ていきましょう。nonlocalは、クロージャ(関数閉包)の実装、カウンターの作成、状態を保持する関数など、さまざまな場面で活躍します。

カウンター関数の実装

nonlocalを使うと、関数を呼び出すたびに値をカウントアップ(1ずつ増やす)するような関数を簡単に作れます。

def create_counter(): count = 0 def counter(): nonlocal count count += 1 return count return counter my_counter = create_counter() print(my_counter()) # 1回目の呼び出し print(my_counter()) # 2回目の呼び出し print(my_counter()) # 3回目の呼び出し

出力結果

1
2
3

この例では、create_counter関数が内側のcounter関数を返しています。nonlocal count宣言により、counter関数が呼び出されるたびに外側のcount変数が更新され、その状態が保持されます。そのため、my_counter()を実行するたびに、countが1ずつ増えていきます。

動物の成長シミュレーション

動物の成長過程をシミュレーションする関数を作ってみましょう。nonlocalを使って年齢を管理します。

def create_pet(name): age = 0 def grow(): nonlocal age age += 1 return f"{name}は{age}歳になりました" return grow my_dog = create_pet("ポチ") print(my_dog()) print(my_dog()) print(my_dog())

出力結果

ポチは1歳になりました
ポチは2歳になりました
ポチは3歳になりました

この例では、create_petが返すgrow関数を実行するたびに、nonlocal ageによって外側のage変数が1ずつ加算されます。create_petが呼び出されたときに作られたage(初期値0)が、my_dog()の呼び出しのたびに更新され、成長が表現されています。

状態を持つ計算機の作成

nonlocalを利用して、簡単な計算履歴(ログ)を保持する計算機を作ってみましょう。

def create_calculator(): history = [] def add(x, y): nonlocal history result = x + y history.append(f"{x} + {y} = {result}") return result def get_history(): return history return {"add": add, "history": get_history} calc = create_calculator() calc["add"](5, 3) calc["add"](2, 7) print(calc["history"]())

出力結果

['5 + 3 = 8', '2 + 7 = 9']

この例では、add関数が呼び出されるたびに、nonlocal historyによって外側のhistoryリストに計算結果の文字列が追加されます。add関数とget_history関数は、create_calculatorの実行時に定義されたhistoryリストを共有しています。

動物園の管理システム

動物園の動物を管理するシステムを作ってみましょう。

def create_zoo(): animals = [] def add_animal(animal): nonlocal animals animals.append(animal) return f"{animal}を追加しました" def list_animals(): return animals return {"add": add_animal, "list": list_animals} zoo = create_zoo() print(zoo["add"]("キリン")) print(zoo["add"]("ゾウ")) print(zoo["list"]())

出力結果

キリンを追加しました
ゾウを追加しました
['キリン', 'ゾウ']

この例では、add_animal関数がnonlocal animals宣言により、外側のanimalsリストを参照しています。.append(animal)でリストに動物が追加されると、list_animals関数も同じリストを参照しているため、追加された動物を含む最新のリストが返されます。

言葉を覚えるオウム関数

言葉を覚えて繰り返すオウム関数を作ってみましょう。

def create_parrot(): words = [] def learn(word): nonlocal words words.append(word) return f"オウムは「{word}」を覚えました" def speak(): return f"オウムは覚えた言葉を話します: {', '.join(words)}" return {"learn": learn, "speak": speak} parrot = create_parrot() print(parrot["learn"]("こんにちは")) print(parrot["learn"]("さようなら")) print(parrot["speak"]())

出力結果

オウムは「こんにちは」を覚えました
オウムは「さようなら」を覚えました
オウムは覚えた言葉を話します: こんにちは, さようなら

この例では、learn関数がnonlocal words宣言によって、外側のwordsリストに新しい単語を追加していきます。speak関数も同じwordsリストを参照しているため、learnで覚えたすべての単語をカンマ(,)でつないで出力できています。

温度変換ツール

温度の単位変換(摂氏から華氏)を行い、最後の変換結果を記録するツールを作成してみましょう。

def create_converter(): last_conversion = None def celsius_to_fahrenheit(c): nonlocal last_conversion f = c * 9/5 + 32 last_conversion = f"{c}℃ → {f}℉" return f def get_last(): return last_conversion return {"convert": celsius_to_fahrenheit, "last": get_last} temp = create_converter() temp["convert"](25) temp["convert"](30) print(temp["last"]())

出力結果

30℃ → 86.0℉

この例では、convert関数(摂氏を華氏に変換)が呼び出されるたびに、nonlocal last_conversionによって外側のlast_conversion変数が上書きされます。temp["convert"](25)が実行された後、temp["convert"](30)が実行されたため、last_conversionには30℃の結果が保存され、temp["last"]()で呼び出されています。

動物の鳴き声カウンター

nonlocalを使って、複数のキーを持つ辞書の値を更新する例です。動物の鳴き声をカウントする関数を作成してみましょう。

def animal_counter(): # 鳴き声の回数を保存する辞書 sounds = {} # 動物の名前を受け取り、カウントする関数 def count_sound(animal_name): nonlocal sounds # もし辞書にその動物がまだ登録されていなければ、0で初期化 if animal_name not in sounds: sounds[animal_name] = 0 # 該当する動物のカウントを1増やす sounds[animal_name] += 1 current_count = sounds[animal_name] # 動物に応じたメッセージを返す if animal_name == "イヌ": return f"ワン!({current_count}回目)" elif animal_name == "ネコ": return f"ニャー!({current_count}回目)" else: return f"{animal_name}が鳴きました({current_count}回目)" # 内側の関数(count_sound)を返す return count_sound # animal_counterを実行し、カウンター関数(count_sound)を変数に保存 my_animal_counter = animal_counter() # カウンター関数を実行 print(my_animal_counter("イヌ")) print(my_animal_counter("ネコ")) print(my_animal_counter("イヌ")) print(my_animal_counter("キリン"))

出力結果

ワン!(1回目)
ニャー!(1回目)
ワン!(2回目)
キリンが鳴きました(1回目)

この例では、animal_counter関数が、内側のcount_sound関数を返します。count_sound関数は、nonlocal sounds宣言によって、外側のsounds辞書を直接操作できます。my_animal_counter("イヌ")のように呼び出すと、sounds辞書の"イヌ"キーの値がチェックされ、存在しなければ0で初期化された後、1が加算されます。"イヌ"を2回呼び出すと、カウントが2になっていることが分かります。

買い物カゴの実装

nonlocalを使用して、オンラインショップの買い物カゴ(辞書)に商品と数量を追加する機能を実装してみましょう。

def create_shopping_cart(): items = {} def add_item(item, quantity=1): nonlocal items if item in items: items[item] += quantity else: items[item] = quantity return f"{item}を{quantity}個カゴに追加しました" def get_items(): return items return {"add": add_item, "items": get_items} cart = create_shopping_cart() print(cart["add"]("リンゴ", 3)) print(cart["add"]("バナナ", 2)) print(cart["items"]())

出力結果

リンゴを3個カゴに追加しました
バナナを2個カゴに追加しました
{'リンゴ': 3, 'バナナ': 2}

この例では、add_item関数がnonlocal items宣言によって、外側のitems辞書を直接操作しています。add_itemが呼び出されるたびに、items辞書に商品が追加されたり、数量が更新されたりします。get_items関数は、更新された最新のitems辞書を返します。

まとめ

Pythonのnonlocal文は、ネスト関数内で外側関数のスコープにある変数を変更するための便利な機能です。この記事では、nonlocalの基本概念から実用的な使用例まで幅広く解説しました。

nonlocal文が活躍する場面は次のようなケースです。

nonlocal文が活躍する場面

  • クロージャ(関数閉包)を実装するとき
  • 関数内で状態(データ)を保持したいとき
  • カウンター関数など、呼び出すたびに値が変わる関数を作るとき

nonlocal文を用いる上で、押さえておきたいポイントを覚えておきましょう。

重要なポイント

  • global文とは違い、外側の「関数」の変数を対象とする
  • nonlocalで指定する変数は、外側の関数で定義済みである必要がある
  • nonlocal x, yのようにカンマ区切りで複数指定もできる

nonlocal文を理解することで、よりすっきりと整理されたコードが書けるようになります。特に状態を持つ関数をシンプルに実装したい場合に役立つでしょう。ただし、使いすぎるとコードの読みやすさが下がる可能性もあるため、必要な場面で適切に活用することがポイントです。

レベルを更に上げたい方はpaizaプログラミングスキルチェックへ

  1. paizaラーニングトップ
  2. リファレンス
  3. Pythonのリファレンス記事一覧
  4. Python nonlocal文の使い方