Python 入門

Python 変数のスコープ

スコープ(Scope)は、プログラムのどの部分から特定の変数にアクセスし、修正できるかを決定します。スコープを理解することは、予期せぬデータの書き換えを防ぎ、プログラムが複雑になってもコードのロジックを正確に把握するために不可欠です。

本章では、Python におけるローカルスコープ(Local Scope)とグローバルスコープ(Global Scope)の概念を深く掘り下げます。

1. ローカル変数

ローカル変数とは、関数内部で定義された変数のことです。そのスコープは、定義された関数内部に厳密に制限されます。つまり、その関数の中でしか変数へのアクセスや修正ができません。関数が実行を終了すると、ローカル変数は破棄され、その値にアクセスすることはできなくなります。

1.1 ローカル変数の例

def my_function():
    # 'x' はローカル変数
    x = 10
    print("関数内部の x の値:", x)

my_function()

# 以下のコードはエラーになります。'x' が関数の外で定義されていないためです。
# print("関数外部の x の値:", x)

この例では、xmy_function 内部のローカル変数です。関数の外からアクセスしようとすると、Python は NameError をスローします。これは、その時点で x がスコープ外になっているためです。

2. ネストされた関数におけるスコープ

ネストされた関数(関数の中で別の関数を定義する場合)では、内側の関数は外側の関数のスコープにある変数にアクセスできます。しかし、外側の関数は内側の関数で定義された変数にアクセスすることはできません。

def outer_function():
    # 'outer_var' は outer_function にとってのローカル変数
    outer_var = 20
    
    def inner_function():
        # 'inner_var' は inner_function にとってのローカル変数
        inner_var = 30
        # inner_function は outer_var にアクセス可能
        print("inner_function 内部から outer_var にアクセス:", outer_var)
        print("inner_function 内部から inner_var にアクセス:", inner_var)
    
    inner_function()
    
    # 以下のコードはエラーになります。'inner_var' は outer_function のスコープでは未定義です。
    # print("inner_function 外部から inner_var にアクセス:", inner_var)

outer_function()

このケースでは、inner_functionouter_function のエンクロージングスコープ(Enclosing Scope)内で定義されているため、outer_var にアクセスできます。しかし、outer_functioninner_function のローカルスコープに閉じ込められている inner_var にはアクセスできません。

3. ローカル変数によるシャドウイング

ローカル変数にグローバル変数と同じ名前を付けた場合、その関数のスコープ内ではローカル変数がグローバル変数をシャドウイング(遮蔽)します。つまり、関数内部ではローカル変数が優先的に使用されます。

global_var = 5

def my_function():
    # グローバル変数 'global_var' が同名のローカル変数によってシャドウイングされる
    global_var = 15
    print("関数内部の global_var の値:", global_var)

my_function()
print("関数外部の global_var の値:", global_var)

出力結果:

関数内部の global_var の値: 15
関数外部の global_var の値: 5

my_function 内部では global_var はローカル変数を指すため値は 15 ですが、関数の外ではグローバル変数を指し続けているため値は 5 のままです。

4. グローバル変数

グローバル変数は、関数の外側で定義される変数です。そのスコープはプログラム全体に及びます。つまり、コード内のどこからでも(関数内部を含めて)アクセスすることができます。

4.1 グローバル変数の例

# 'global_variable' はグローバル変数
global_variable = 100

def my_function():
    print("関数内部の global_variable の値:", global_variable)

my_function()
print("関数外部の global_variable の値:", global_variable)

この例では、global_variablemy_function の内部と外部の両方からアクセス可能です。

5. 関数内でのグローバル変数の変更

関数からグローバル変数に直接アクセスすることは可能ですが、関数内部でその値を変更したい場合は、global キーワードを使用する必要があります。

global キーワードを使わずに同名の変数に値を代入しようとすると、Python はそれを関数スコープ内の新しいローカル変数の作成(前述のシャドウイング)として扱います。

global_variable = 200

def modify_global():
    global global_variable  # グローバル変数を使用することを宣言
    global_variable = 300
    print("modify_global 内部の global_variable の値:", global_variable)

modify_global()
print("modify_global 外部の global_variable の値:", global_variable)

出力結果:

modify_global 内部の global_variable の値: 300
modify_global 外部 of global_variable の値: 300

global キーワードを使用することで、関数内部で変更したいのは global_variable という名前のグローバル変数であることを Python に明示的に伝えています。

6. グローバル変数の使用タイミング

グローバル変数は便利ですが、使用は最小限に抑えるべきです。多用するとコードの理解、デバッグ、メンテナンスが困難になります。一般的にグローバル変数が適しているケースは以下の通りです:

  • 定数 (Constants): プログラム全体で変わることのない値を定義する場合。
  • 設定 (Configuration Settings): プログラムの異なる部分からアクセスする必要があるグローバルな設定項目。
  • 共有状態 (Shared State): 特定の状況下でモジュール間で状態を共有する必要がある場合。ただし、慎重な取り扱いが必要です。

7. nonlocal キーワード

nonlocal キーワードはネストされた関数で使用され、直近のエンクロージングスコープ(グローバルではない外側のスコープ)の変数を参照するために使われます。

内側の関数から外側の関数の変数を変更したいが、新しいローカル変数を作りたくないという場合に役立ちます。

7.1 nonlocal の例

def outer_function():
    outer_var = 10
    
    def inner_function():
        nonlocal outer_var  # エンクロージングスコープの outer_var を参照
        outer_var = 20
        print("inner_function 内部の outer_var の値:", outer_var)
    
    inner_function()
    print("outer_function 内部の outer_var の値:", outer_var)

outer_function()

出力結果:

inner_function 内部の outer_var の値: 20
outer_function 内部の outer_var の値: 20

nonlocal を使用しない場合、inner_function は独自のローカル変数 outer_var を作成してしまいます。nonlocal を使うことで、outer_function のスコープに属する outer_var を正常に修正できました。

8. 実践ケースとデモンストレーション

理解を深めるために、より複雑な例を見てみましょう。

8.1 ケース 1:割引を適用した合計金額の計算

discount_rate = 0.1  # 割引率のグローバル変数

def calculate_price(price, quantity):
    """割引を適用した後の合計金額を計算する。"""
    global discount_rate # グローバル変数を使用 (読み取りだけなら省略可能)
    
    # 割引の適用
    discount = price * discount_rate
    final_price = (price - discount) * quantity
    return final_price

# 使用例
price = 100
quantity = 5
total_price = calculate_price(price, quantity)
print("合計金額:", total_price) # 出力: 450.0

この例では、discount_rate がグローバル変数として定義され、calculate_price 関数内で計算に使用されています。

8.2 ケース 2:nonlocal を使用したカウンターの実装

def counter():
    count = 0 # counter 内に閉じ込められた変数
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment # increment 関数自体を返す

# カウンターのインスタンスを作成
my_counter = counter()

# increment 関数を複数回呼び出す
print(my_counter()) # 出力: 1
print(my_counter()) # 出力: 2
print(my_counter()) # 出力: 3

ここでは、counter 関数が increment 関数自体を返しています(これは Python におけるクロージャ (Closure) と呼ばれる仕組みです)。my_counter() を呼び出すたびに、counter のエンクロージングスコープにある count 変数が更新されます。nonlocal がなければ、呼び出すたびに新しいローカル変数 count が作られてしまい、値を累積させることはできません。