はじめに
Python でコードを書くときにグローバル変数とローカル変数の扱いに迷う場面は少なくありません。
特に Java や C# のようなブロックごとにスコープが分かれる言語に慣れていると Python の挙動は直感と違って戸惑うことがあります。
この記事では学習課題でカレンダーを実装する過程で整理した内容をベースに、
Python の変数スコープを グローバル・ローカル・global・nonlocal に分けてまとめます。
グローバル変数に置くべきもの
絶対に書き換えない値(定数)
例:曜日リスト、バージョン番号、設定値など
WEEKDAYS = ["月", "火", "水", "木", "金", "土", "日"]
VERSION = "1.0.0"
→ 大文字で書くのが慣習(「定数ですよ」と伝えるため)。
複数の関数で共通して参照したい固定データ
例:曜日ヘッダを出力関数と計算関数で共通利用する場合。
ローカル変数にすべきもの
処理の途中で変わる値や一時的なデータ
例:日付計算の結果、ループカウンタ、ユーザー入力など。
def main():
today = datetime.now()
input_date = today.replace(day=1)
その関数にだけ関係するデータ
外から見えないほうが安全で関数の責務も明確になる。
原則ルール
-
読み取り専用ならグローバルに置いてOK(定数扱い)。
-
書き換える可能性があるなら関数内に閉じ込める。
迷ったときの判断ポイント
「他の関数から直接使いたい?」
→ YES → グローバル(ただし定数的なもののみ)
→ NO → ローカル
「将来テストや再利用時に勝手に変わると困る?」
→ 困る → ローカル
→ 困らない(固定データ) → グローバル
カレンダー課題での具体例
-
WEEK_HEADER(固定の曜日ヘッダ)
→ グローバル定数でOK -
input_date、end_of_month、weeks_l
→ ローカル変数(毎月変わる値だから)
Pythonのスコープ挙動
1. グローバル変数とローカル変数の「かぶり」
Pythonでは関数内で代入をするとその名前は「ローカル変数」として扱われる。
そのため、モジュール直下(グローバル)で定義済みの変数があっても関数内で代入する処理があると「参照より前」にエラーが出るケースがある。
x = 10 # グローバル変数
def func():
print(x) # ←ここで参照したい
x = 5 # ←代入があるため、xはローカル扱いになる
func()
# UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
このように 「代入があるかどうか」でローカル扱いになるかが決まる ので注意が必要。
回避する方法2つ
-
グローバル変数を使うと明示する → global x(非推奨)
-
そもそも関数内で直接代入せず引数や戻り値でやり取りする(こちらが推奨)
2. Python は「関数スコープ」
if / for / while / try / except などのブロックごとにスコープは区切られず、関数単位でまとめて扱われる。
def sample():
if True:
x = 10
print(x) # OK → 10
3. 代入されないと未定義エラー
例外などで代入が飛んだ場合その後の参照でエラーになる。
def bad():
try:
z = int("abc") # 例外発生
except ValueError:
pass
print(z) # NameError: z is not defined
4. Java / C# との違い
Java / C# は {} ごとにスコープが分かれるが Python は関数全体で共有される。
そのため ブロックをまたいで変数を共有できる 。
5. カレンダー課題で遭遇したケース
実際にカレンダー課題を実装する中で遭遇したのがこのようなケースです。
try:
m = int(sys.argv[2]) # try 内で定義
except ValueError:
sys.exit(1)
# try の外でも m を使える(関数スコープだから)
beginning_of_month = today.replace(month=m, day=1)
global キーワードについて
モジュール直下の変数を関数内で変更するときに使う。
便利だが副作用が大きくテストや再利用性を損なうため一般的に非推奨。
基本は 「定数を参照するだけ」 に留めるのが安全。
count = 0
def add():
global count
count += 1
→ 小規模スクリプトなら手軽だが、本格的なコードでは避けるのが無難。
nonlocal キーワードについて
ネストした関数の中から「一つ外側のスコープ」にある変数を変更したいときに使う。
主に クロージャで状態を持たせるとき に便利。
def outer():
x = 0
def inner():
nonlocal x
x += 1
return x
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
使いどころはかなり限定的。
「クロージャを活用する特殊ケース」でしか見ないため無理に使う必要はない。
グローバルに置いてよい例外
定数(書き換えない値)
WEEK_HEADER = "月 火 水 木 金 土 日"
設定値(環境に依存する固定値)
例:デフォルトのファイルパスやAPIのエンドポイント。
グローバル変数を避けるべき理由
-
可読性が下がる(どこで値が変わったか追いにくい)
-
テストしづらい(初期化しないと動かない)
-
バグの温床(複数の関数で書き換えると壊れる)
グローバルを避ける方法
-
関数の引数・戻り値で受け渡す → 最もシンプルで安全。
-
必要ならクラス化 → 状態をまとめて持たせたいときはインスタンス変数に。
まとめ
-
スクリプト的に書くとき → グローバルは定数だけ
-
動的に変わる値は必ず関数ローカルに閉じ込める
-
状態を持たせたいならクラスを使う
-
JavaやC#からPythonに来た人ほどスコープの違いに気をつけた方がいい
今回のカレンダー課題を通じて「定数はグローバル・処理結果はローカル」というシンプルな原則を意識することの大切さを再確認できた。
前回記事の紹介
この記事の前段として、コマンドライン引数 sys.argv を使ったカレンダー実装について整理しています。
興味のある方はこちらもどうぞ。(外部ブログ)
【Python】sys.argv とカレンダー課題で得た知見まとめ
クラス変数とインスタンス変数について
クラス変数とインスタンス変数の違いは Java などに慣れていると直感で理解しやすい部分ですが、Python 独自のポイントもあります。
このあたりは別の記事でまとめる予定です。