【Python】Pythonのスコープとは?グローバル変数・ローカル変数の違いとglobal文の使い方

Python

こんにちは、かつコーチです。

今回は、Pythonで関数を書き始めた人がほぼ全員一度はつまずく「スコープ」について解説します。

スコープとは、変数が参照できる範囲のことです。

「関数の中で定義した変数が、関数の外から見えない」「逆に、関数の外の変数を関数の中で書き換えようとしたらエラーになった」といった経験はないでしょうか。

私自身、駆け出しの頃にこの仕組みを理解しないまま関数を書いていて、UnboundLocalErrorという謎のエラーに何十分も悩まされたことがあります。

この記事を読めば、ローカル変数とグローバル変数の違い、global文の使い方、そして意図せずローカル変数扱いになってしまうハマりどころまで、まとめて理解できます。

スコープとは?ローカル変数とグローバル変数の基本

ローカル変数とグローバル変数の定義

まず、2つの用語を整理します。

  • ローカル変数:関数の中で定義され、その関数の中でしか参照できない変数
  • グローバル変数:関数の外(モジュールの最上位)で定義され、モジュール全体から参照できる変数

コードで見てみましょう。

message = "こんにちは"  # グローバル変数

def greet():
    name = "かつコーチ"  # ローカル変数
    print(message, name)

greet()
print(message)
# print(name)  # ここでNameErrorになる

messageは関数の外で定義されているのでグローバル変数、nameは関数の中で定義されているのでローカル変数です。

greet()関数の中からは、グローバル変数のmessage読み取ることができます

一方、namegreet()関数の中でしか存在しないため、関数の外でprint(name)を実行するとNameErrorになります。

なぜスコープという仕組みが必要なのか

「全部グローバル変数にすれば楽なのでは」と思うかもしれません。

しかし、関数がすべての変数を共有してしまうと、次のような問題が起こります。

  • ある関数の中でうっかり変数を上書きしてしまい、別の関数の動作がおかしくなる
  • 変数名の衝突を避けるために、常に長くて分かりにくい変数名を使う必要が出てくる
  • コードのどこで変数が変更されたのか追跡しづらくなる

ローカル変数の仕組みがあることで、関数の中は関数の中だけで完結するという安心感が生まれます。

これはPythonに限らず、多くのプログラミング言語で採用されている考え方です。

global文の使い方

関数の中からグローバル変数を「書き換える」には

先ほどの例のように、関数の中からグローバル変数を読むだけなら、特別な記述は不要です。

しかし、関数の中でグローバル変数に代入(書き換え)をしようとすると、話が変わってきます。

count = 0

def increment():
    count = count + 1  # ここでエラーになる
    print(count)

increment()

実行すると、次のようなエラーが出ます。

UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

私が最初にこのエラーを見たときは、「グローバル変数のcountはちゃんと0で定義してあるのに、なぜ『値がない』と言われるのか」と混乱しました。

原因は、関数の中にcount = count + 1という代入文があることです。

Pythonは関数の中に代入文を見つけると、その時点で「この関数の中ではcountはローカル変数として扱う」と決めてしまいます。

その結果、右辺のcountを評価する段階では、まだローカル変数のcountに値が入っていないため、UnboundLocalErrorになるのです。

global文でグローバル変数だと明示する

この問題を解決するのがglobalです。

global文を使うと、「この変数は関数の外にあるグローバル変数を使う」とPythonに明示できます。

count = 0

def increment():
    global count
    count = count + 1
    print(count)

increment()  # 1
increment()  # 2
print(count)  # 2

global countと宣言することで、関数の中のcountはグローバル変数のcountそのものを指すようになります。

そのため、関数を呼び出すたびにグローバル変数のcountが更新されていきます。

globalを使うときの注意点

global文は関数の先頭付近、変数を使う前に書くのが基本です。

def increment():
    count = count + 1  # ❌ global宣言より先に使ってしまっている
    global count

このようにglobal宣言より前に変数を使ってしまうと、SyntaxErrorになります。

SyntaxError: name 'count' is used prior to global declaration

global文は必ず、その変数を使い始める前に書くようにしましょう。

よくあるつまずきポイント・エラー対処

つまずき1:関数内で新しい変数のつもりが同名だった

意図せずローカル変数扱いになってしまう、代表的なケースを紹介します。

❌ Before:グローバル変数のつもりが別のローカル変数になっていた

total = 0

def calculate(price, tax_rate):
    total = price * (1 + tax_rate)  # グローバルのtotalを更新したいつもり
    return total

calculate(1000, 0.1)
print(total)  # 0のまま(意図と違う)

このコードを書いた本人は「合計金額をグローバル変数のtotalに反映させたい」と思っているかもしれません。

しかし、実際には関数内のtotal = ...によって、関数内だけで完結する新しいローカル変数totalが作られています。

そのため、関数の外のtotal0のまま変化しません。

実際に私も、複数のファイルにまたがる計算処理で「合計が更新されない」というバグに遭遇し、原因を探ったところ、この「同名だが別物のローカル変数」が原因でした。

✅ After:戻り値で結果を受け取るか、globalを明示する

同じ処理を、意図が明確になるように書き直します。

total = 0

def calculate(price, tax_rate):
    result = price * (1 + tax_rate)
    return result

total = calculate(1000, 0.1)
print(total)  # 1100.0

多くの場合、グローバル変数を書き換えるより、戻り値(return)で結果を受け渡す方が安全です。

戻り値とは、関数の処理結果として呼び出し元に返される値のことです。

どうしてもグローバル変数自体を更新したい場合は、先ほど紹介したglobal文を明示的に使います。

total = 0

def calculate(price, tax_rate):
    global total
    total = price * (1 + tax_rate)

calculate(1000, 0.1)
print(total)  # 1100.0

「グローバル変数を書き換えたいのか、新しいローカル変数を作りたいだけなのか」を意識して書くことが、このバグを避ける一番のポイントです。

つまずき2:リストや辞書は代入と勘違いしやすい

もう1つ、初中級者がよく混乱するのが「変更(ミューテーション)」と「代入」の違いです。

ミューテーションとは、変数の中身(オブジェクト)そのものを変更する操作のことです。

items = []

def add_item(name):
    items.append(name)  # これは代入ではなくミューテーション

add_item("りんご")
add_item("みかん")
print(items)  # ['りんご', 'みかん']

items.append(name)items = ...という代入文ではないため、Pythonはitemsをローカル変数だと判断しません。

そのため、global文がなくても、関数の中からグローバル変数のリストに要素を追加できます。

一方、次のように書くと動きが変わります。

items = []

def reset_items():
    items = []  # これは代入なので、ローカル変数になる
    print(items)  # []

reset_items()
print(items)  # ['りんご', 'みかん'] のまま変わらない

「関数の中で=を使って代入しているかどうか」が、ローカル変数扱いになるかどうかの境界線になります。

この違いを意識しておくと、リストや辞書がらみのスコープの混乱をかなり減らせます。

応用・一歩先の使い方

nonlocal文:関数の中の関数でスコープを扱う

関数の中に、さらに関数を定義する(入れ子関数)ケースでは、globalではなくnonlocal文を使います。

def make_counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

nonlocalは、「1つ外側の関数のローカル変数」を書き換えたいときに使います。

globalが「モジュール全体のグローバル変数」を対象にするのに対し、nonlocalは「外側の関数のスコープ」を対象にする点が違いです。

このような、外側の変数を覚えている関数のことをクロージャと呼びます。

カウンターやキャッシュの仕組みを作るときによく登場するパターンなので、覚えておくと応用が効きます。

実務では「グローバル変数を極力使わない」設計が基本

ここまでglobal文の使い方を解説してきましたが、実務のコードではグローバル変数の書き換えはできるだけ避けるのがセオリーです。

理由は、グローバル変数を複数の関数から書き換えられるようにすると、「どの関数がいつ値を変えたのか」が追いづらくなり、バグの温床になりやすいからです。

代わりに、関数の引数として値を渡し、戻り値として結果を受け取る設計にすると、処理の流れが追いやすくなります。

# グローバル変数を書き換える設計(避けたい)
total = 0
def add_price(price):
    global total
    total += price

# 引数と戻り値でやり取りする設計(推奨)
def add_price(total, price):
    return total + price

total = 0
total = add_price(total, 1000)
total = add_price(total, 500)
print(total)  # 1500

global文の存在自体は知っておく必要がありますが、「使わずに済む設計ができないか」を先に考える習慣をつけましょう。

まとめ

この記事のポイント

  • ローカル変数は関数の中だけで有効、グローバル変数はモジュール全体で有効
  • 関数の中でグローバル変数に代入すると、UnboundLocalErrorや意図しないローカル変数化が起こる
  • グローバル変数を書き換えたいときはglobal文を、変数を使う前に宣言する
  • リストや辞書へのappendなどのミューテーションは代入ではないため、globalなしでも変更できる
  • 入れ子関数の外側の変数を書き換えたいときはnonlocal文を使う
  • 実務ではグローバル変数の書き換えを避け、引数と戻り値でやり取りする設計が基本

次に読むべき記事

スコープの仕組みが分かったら、次は関数の柔軟な引数の受け取り方を押さえていきましょう。

→ 次の記事:Pythonの可変長引数(*args・**kwargs)の使い方

タグ: Python, 中級者向け, 基本文法

タイトルとURLをコピーしました