閉じる

【Python】変数スコープ入門:global・local・非ローカルを図解で徹底理解

Pythonの変数スコープは、プログラムの読みやすさやバグの少なさに直結する大切な概念です。

特に、関数内と関数外で同じ名前の変数を使う場面や、ネストした関数(関数の中に関数がある状態)では、スコープを正しく理解していないと意図しない動作を招きます。

この記事では、global・local・nonlocalの3つのキーワードとスコープルールを、図解とサンプルコードを交えながら丁寧に解説します。

変数スコープとは

変数スコープ(global・local)の基本概念

プログラムの中で使う変数には、「どこから見えるか」(有効範囲)という考え方があります。

この有効範囲のことを「スコープ(scope)」と呼びます。

Pythonでは、代表的なスコープとして次の2つが登場します。

1つ目はグローバルスコープ(global scope)です。

これは、1つのPythonファイル(モジュール)全体から参照できる領域を指します。

ファイルの先頭レベルで定義した変数はグローバル変数となり、同じファイル内のどこからでも読み取ることができます。

2つ目はローカルスコープ(local scope)です。

これは通常、関数の内部で使われる一時的な領域を指します。

関数内で代入(=)した変数は、その関数の中からだけ見えるローカル変数となり、関数の外側から直接触ることはできません。

このように、同じ変数名であっても、どのスコープに属するかによって「見える場所」「寿命」が変わる点が、スコープ理解の第一歩になります。

Pythonにおけるスコープルールの仕組み

Pythonには、変数の名前をどこから探すかを決める「LEGBルール」があります。

これは次の4段階を表す略称です。

  1. Local(ローカルスコープ)
  2. Enclosing(外側の関数のローカルスコープ)
  3. Global(モジュール全体のスコープ)
  4. Built-in(Pythonが最初から用意している名前のスコープ)

Pythonがある名前を評価するとき、その名前がどこのスコープにあるかを、上記の順に探していきます。

たとえば関数の中でxという変数名を参照した場合、まずその関数のローカル変数の中を探し、それでもなければ一段外側の関数、その次にグローバルスコープ、最後に組み込み(Built-in)の名前空間を探します。

このルールを理解しておくと、同じ名前の変数が複数のスコープに存在するとき、どれが参照されるかを正しく予測できるようになります。

ローカル変数(local)の基礎

ローカルスコープとは何か

ローカルスコープとは、関数が呼び出されたときに、その関数専用に一時的に作られる名前空間です。

関数の処理が終われば、そのローカルスコープは破棄され、通常はそこにあった変数には二度とアクセスできません。

ローカルスコープの特徴は次の通りです。

関数を呼び出すたびに、新しいローカルスコープが生成されます。

同じ関数を複数回呼び出した場合でも、それぞれの呼び出しでローカル変数は独立しており、他の呼び出しと干渉しません。

このため、関数は「入力から出力を計算する小さな箱」として安全に扱えるようになります。

関数内の変数定義と参照のルール

Pythonでは、関数内で代入文(=)を行った変数は、自動的にその関数のローカル変数として扱われます。

この基本ルールをまず押さえておきましょう。

次のコードで確認してみます。

Python
# 関数内での変数定義と参照の基本

def my_func():
    # 関数の中で x に代入しているので、これはローカル変数 x です
    x = 10
    print("my_func 内の x:", x)

# 関数の外側(グローバルスコープ)
my_func()

# 関数の外からローカル変数 x にアクセスしようとするとエラーになります
print("関数の外から x を参照:", x)  # NameError になる

上記コードを実行すると、最後のprintNameErrorが発生します。

これは、ローカル変数はその関数の外からは見えないためです。

以下は、上記のコードを実行したときの典型的な出力例です。

実行結果
my_func 内の x: 10
Traceback (most recent call last):
  File "example.py", line 11, in <module>
    print("関数の外から x を参照:", x)
NameError: name 'x' is not defined

この挙動から、「関数内で代入した変数は、その関数の外では存在しない」という事実が分かります。

ローカル変数と同名のグローバル変数の優先順位

グローバルスコープに同じ名前の変数が存在していても、関数内で同じ名前をローカル変数として定義した場合、関数内ではローカル変数が優先されます

次のサンプルコードで確かめてみます。

Python
# グローバル変数
x = 100

def show_x():
    # 同じ名前のローカル変数 x を定義
    x = 10
    print("関数内の x:", x)  # ローカル変数 x が使われる

print("関数の外の x:", x)  # グローバル変数 x が使われる
show_x()
print("関数の外の x (再び):", x)  # グローバル変数 x は変化していない
実行結果
関数の外の x: 100
関数内の x: 10
関数の外の x (再び): 100

この例から分かるように、ローカル変数とグローバル変数が同名で存在しても、互いに独立しているため、関数内での代入はグローバル変数に影響を与えません。

LEGBルールに従い、「まずローカルスコープを探し、見つかればそこで確定」となるためです。

グローバル変数(global)の使い方

グローバルスコープとは何か

グローバルスコープとは、Pythonファイル(モジュール)全体から見える名前空間です。

ファイルのトップレベル(インデントされていない位置)で定義した変数は、すべてグローバル変数として扱われます。

たとえば次のようなコードを考えます。

Python
# これはグローバルスコープ
counter = 0  # グローバル変数

def show_counter():
    # 関数内からでも、グローバル変数を「読み取る」ことはできます
    print("カウンタ:", counter)

show_counter()
print("グローバルから参照:", counter)

このように、関数内からグローバル変数を「参照するだけ」であれば、特別なキーワードは不要です。

ただし、グローバル変数を書き換えたいときには、次に説明するglobalキーワードが必要になります。

globalキーワードの基本と書き方

Pythonでは、関数内でグローバル変数を書き換えるにはglobalキーワードが必須です。

これを付けることで、「この変数名はローカルではなく、グローバル変数を指す」という宣言になります。

次の例で具体的に見てみましょう。

Python
counter = 0  # グローバル変数

def increment():
    global counter  # これにより、以下の counter はグローバルを指す
    counter = counter + 1  # グローバル変数 counter を更新

def show():
    print("現在のカウンタ:", counter)

show()       # まだ 0
increment()  # グローバル counter を +1
show()       # 1 が表示される
increment()
show()       # 2 が表示される

想定される出力は次の通りです。

実行結果
現在のカウンタ: 0
現在のカウンタ: 1
現在のカウンタ: 2

もしglobal counterを書き忘れると、Pythonは関数内のcounterをローカル変数だと解釈し、次のようなエラーが出ます。

Python
counter = 0

def bad_increment():
    # global を書いていない
    counter = counter + 1  # UnboundLocalError になる

bad_increment()

この場合の典型的なエラーメッセージは次のようになります。

実行結果
UnboundLocalError: cannot access local variable 'counter' where it is not associated with a value

これは、代入文が存在するために counter がローカル変数とみなされるが、代入前に参照しているので「まだ値がないローカル変数を使おうとした」と解釈された結果です。

global変数を使う際の注意点と設計指針

global変数は便利な反面、使いすぎるとプログラムが理解しづらくなります

特に大規模なコードベースや複数人での開発では、グローバル変数がどこからでも変更できてしまうため、「いつ、どこで値が変わったのか分かりにくい」という問題が発生します。

設計上の指針としては、次のように考えるとよいです。

まず、グローバル変数は「定数」もしくは「設定値」のような、基本的に書き換えない値に限定して使うことを検討します。

たとえばPI = 3.14159MAX_RETRY = 3といった値は、グローバルに置いても問題は比較的少ないです。

一方で、状態が変化する値(カウンタ、フラグ、キャッシュなど)は、できるだけ関数の引数や戻り値、あるいはクラスのインスタンス変数として管理することが推奨されます。

これにより、「どの関数がどの状態を受け取り、どのように更新しているか」が明確になり、バグの特定やテストが容易になります。

どうしてもglobalを使う必要がある場合は、globalキーワードを使っている箇所をできるだけ限定し、「この関数はグローバル状態を変更する特別な処理である」ということが分かるように設計するとよいでしょう。

非ローカル変数(nonlocal)とクロージャ

nonlocalキーワードとは何か

Pythonにはglobalに似たキーワードとして、nonlocalがあります。

これは「グローバルではないが、自分の一つ外側(あるいはさらに外側)の関数スコープにある変数」を参照・更新するためのものです。

nonlocalが使えるのは、ネストした関数(関数の中に関数が定義されている構造)の中だけです。

具体的には、次のような状況で登場します。

  1. 外側の関数outer()で変数xを定義する
  2. 内側の関数inner()から、このxを書き換えたい

このとき、inner()の中でnonlocal xと宣言することで、外側outer()のローカル変数xを更新できるようになります。

ネストした関数と変数スコープの関係

ネストした関数では、外側の関数のローカルスコープが「Enclosingスコープ」として内側から参照可能になります。

これがLEGBルールの「E」にあたります。

次のコードは、内側の関数から外側の変数を「参照するだけ」の例です。

Python
def outer():
    message = "こんにちは"  # outer のローカル変数

    def inner():
        # outer の message を参照できる(読み取り専用)
        print("inner から参照:", message)

    inner()  # inner を呼び出す

outer()
実行結果
inner から参照: こんにちは

ここではinner()の中でmessageに代入していないため、outer のローカル変数をそのまま読み取るだけならnonlocalは不要です。

しかし、inner()の中でmessageを書き換えたい場合は話が変わります。

このときはnonlocalの出番です。

nonlocalで外側のローカル変数を更新する方法

nonlocal の真価が発揮される典型例が「クロージャ」と呼ばれるパターンです。

クロージャとは、「関数が定義されたときの外側の状態(変数)を覚えたまま、後からでもその状態を利用できる関数」のことです。

次のコードは、nonlocalを使って「呼び出すたびにカウントアップする関数」を生成する例です。

Python
def make_counter():
    # 外側の関数のローカル変数
    count = 0

    def increment():
        # nonlocal により、内側から外側の count を更新できる
        nonlocal count
        count = count + 1
        return count

    # 内側の関数を「関数オブジェクト」として返す
    return increment

# カウンタ関数を2つ生成
counter1 = make_counter()
counter2 = make_counter()

# counter1 の動作確認
print("counter1:", counter1())  # 1
print("counter1:", counter1())  # 2

# counter2 は独立したカウンタ
print("counter2:", counter2())  # 1
print("counter2:", counter2())  # 2

# counter1 は引き続き 3, 4... と増えていく
print("counter1:", counter1())  # 3

想定される出力は次のようになります。

実行結果
counter1: 1
counter1: 2
counter2: 1
counter2: 2
counter1: 3

このコードでは、make_counter()を呼び出すたびに、新しいcountを持ったincrement()関数が作られます。

increment()の中でnonlocal countと宣言しているため、count = count + 1make_counter()のローカル変数countを更新します。

このように「外側の関数が終わったあとでも、そのローカル変数が内側の関数によって保持される」状態は、クロージャと呼ばれ、状態を持つ関数を手軽に作る手法としてよく利用されます。

なお、nonlocalはあくまで「グローバルではない外側スコープの変数」を扱うためのものであり、グローバル変数を更新したい場合はglobalを使う必要があります

両者の違いを意識して使い分けることが重要です。

まとめ

Pythonの変数スコープは、LEGBルール(Local→Enclosing→Global→Built-in)に従って名前を解決する仕組みです。

関数内での代入は自動的にローカル変数となり、同名のグローバル変数より優先されます。

グローバル変数を書き換えるにはglobal、ネストした関数で外側のローカル変数を書き換えるにはnonlocalが必要です。

globalの多用は設計を複雑にするため、できるだけ引数・戻り値やクラスを使う設計を心がけつつ、nonlocalとクロージャを活用すると、より表現力豊かで保守しやすいPythonコードを書くことができます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!