Pythonでの変数はどこから見えるか(スコープ)を理解すると、意図しないバグを大幅に減らせます。
本記事では、初心者がつまずきやすいローカル変数とグローバル変数の挙動を、短いコードと出力を通して丁寧に解説します。
読み書きの規則と注意点を押さえて、安全で読みやすいコードを書けるようになりましょう。
Pythonのスコープとは
変数が使える範囲(スコープ)の基本
スコープとは、変数名が有効な範囲のことを指します。
Pythonでは関数を境にスコープが分かれ、関数内で定義した変数は関数の外から見えません。
一方、ファイル直下(モジュールレベル)で定義した変数は、基本的にそのファイル内の関数から読むことができます。
Pythonのスコープ探索は一般にLEGB
(Local→Enclosing→Global→Builtins)という規則で行われますが、本記事ではLocalとGlobalを中心に扱います。
以下はスコープの概観です。
名称 | 例 | 役割 | 備考 |
---|---|---|---|
ローカル(Local) | 関数内の変数や引数 | 関数内だけで有効 | 関数を抜けると参照不可 |
外側(Enclosing) | ネストした外側関数の変数 | 内側関数から参照可 | 変更はnonlocal が必要(本記事範囲外) |
グローバル(Global) | ファイル直下の変数 | 同じファイルの関数から参照可 | 変更はglobal が必要 |
組み込み(Builtins) | len やprint | どこからでも参照可 | 組み込み名の上書きに注意 |
スコープは「どこから見えるか」の話で、オブジェクトの寿命(ライフタイム)とは別概念です。
例えば関数がオブジェクトを返せば、関数の外でもそのオブジェクト自体は生き続けます。
ローカル変数とグローバル変数の違い
ローカル変数は関数の中だけ、グローバル変数はモジュール全体という違いがあります。
以下の例では、関数内で作ったy
は外から見えず、NameError
になります。
# グローバル変数
x = 10
def show():
# ローカル変数
y = 5
print("関数内で見えるx:", x) # グローバル変数は関数内から「読み取り」は可能
print("関数内で見えるy:", y) # ローカル変数は関数内では見える
show()
print("関数外で見えるx:", x)
print("関数外で見えるy:", y) # ここはエラーになる
関数内で見えるx: 10
関数内で見えるy: 5
関数外で見えるx: 10
NameError: name 'y' is not defined
ポイント: y
はshow
のローカル変数なので関数外からアクセスできません。
関数内と関数外の見え方
関数内からグローバルは読めますが、ローカルは関数外に漏れません。
安全に確認するため、外からのアクセスでは例外を捕まえます。
b = 42 # グローバル
def function_scope():
a = "local only"
print("関数内からaは見える:", a)
def global_scope():
print("関数内からbは見える:", b) # グローバルの読み取り
function_scope()
global_scope()
print("関数外からbは見える:", b)
try:
print("関数外からaは見える:", a)
except NameError as e:
print("エラー:", e)
関数内からaは見える: local only
関数内からbは見える: 42
関数外からbは見える: 42
エラー: name 'a' is not defined
Pythonのローカル変数の基礎
関数で作られ関数外では使えない
ローカル変数は作られた関数の中でしか使えません。
関数の外から参照するとNameError
になります。
def make_local():
note = "I live in make_local"
print("関数内:", note)
make_local()
try:
print("関数外:", note) # ローカル変数は関数外から見えない
except NameError as e:
print("エラー:", e)
関数内: I live in make_local
エラー: name 'note' is not defined
関数が終わると、note
という名前は解決できなくなります。
ただし関数内で作ったオブジェクト自体が外に返されていれば、オブジェクトは生き続けます。
引数はローカル変数として扱われる
関数の引数もローカル変数です。
同名のグローバル変数があっても、引数名が優先して使われます。
コード例1(数値)
total = 100 # グローバル
def add_and_show(total, add):
# ここでの total は引数(=ローカル)。グローバルの total とは別物。
total = total + add
print("関数内のtotal:", total)
return total
print("関数呼び出し前のglobal total:", total)
new_total = add_and_show(total, 50)
print("関数呼び出し後のglobal total:", total)
print("戻り値new_total:", new_total)
関数呼び出し前のglobal total: 100
関数内のtotal: 150
関数呼び出し後のglobal total: 100
戻り値new_total: 150
コード例2(ミュータブルなオブジェクト)
items = [1, 2, 3]
def append_item(lst):
# lst はローカル変数だが、同じリストオブジェクトを参照している
lst.append(4)
print("関数内のlst:", lst)
append_item(items)
print("関数外のitems:", items)
関数内のlst: [1, 2, 3, 4]
関数外のitems: [1, 2, 3, 4]
引数名はローカルですが、ミュータブルなオブジェクトを渡すと中身の変更は呼び出し元にも影響します。
必要に応じてコピー(list(x)
やx.copy()
)を渡すと安全です。
同名ならローカル変数が優先
関数内で代入がある名前は、その関数のローカルとみなされます。
同名のグローバルは見えなくなります。
コード例1(上書きではなく「隠す」)
x = 1 # グローバル
def local_priority():
x = 99 # 同名のローカル変数がグローバルを隠す
print("関数内のx:", x)
local_priority()
print("関数外のx:", x) # グローバルは変わっていない
関数内のx: 99
関数外のx: 1
コード例2(よくあるエラー: UnboundLocalError)
x = 1
def read_then_assign():
try:
print("代入前にxを参照:", x) # ここでエラー
x = 2 # この代入があるため、x はローカル扱いになる
except UnboundLocalError as e:
print("UnboundLocalError:", e)
read_then_assign()
UnboundLocalError: local variable 'x' referenced before assignment
関数内で代入が1回でもあると、その名前は関数全体でローカル扱いです。
グローバルを読みたいだけなら、関数内で同名の代入を避けるか、次章のglobal
を使う必要があります。
Pythonのグローバル変数の基礎
関数の外で定義し関数から読める
グローバル変数は、同じファイル内の関数から宣言なしで読み取れます。
rate = 1.1 # グローバル
def apply_rate(price):
# global 宣言なしに読み取りは可能
return int(price * rate)
print(apply_rate(100))
110
関数内で変更するにはglobalが必要
関数内でグローバル変数を更新するにはglobal
宣言が必須です。
無いとUnboundLocalError
になります。
コード例1(globalなしで失敗)
count = 0
def increment_wrong():
# 下の代入があるため count はローカル扱い → 右辺の count が未定義になる
count = count + 1
try:
increment_wrong()
except UnboundLocalError as e:
print("エラー:", e)
エラー: local variable 'count' referenced before assignment
コード例2(globalありで成功)
count = 0
def increment():
global count # グローバル変数を更新する宣言
count += 1
print("関数内のcount:", count)
increment()
increment()
print("関数外のcount:", count)
関数内のcount: 1
関数内のcount: 2
関数外のcount: 2
グローバル変数のデメリット
グローバルは手軽ですが、コードの見通しとテスト容易性を下げることが多いです。
主なデメリットは以下です。
- 隠れた依存関係が生まれ、関数の入出力だけでは挙動が読めない
- 並行処理や複数箇所からの更新で予期せぬ状態になる
- モジュール間で同名の衝突や上書きが起こり得る
コード例(共有状態による影響)
discount = 0.1 # 共有状態(グローバル)
def set_campaign(on):
global discount
discount = 0.3 if on else 0.1 # グローバルを書き換える
def calc(price):
return int(price * (1 - discount)) # グローバルに依存
print("通常:", calc(1000))
set_campaign(True)
print("キャンペーン中:", calc(1000))
set_campaign(False)
print("終了後:", calc(1000))
通常: 900
キャンペーン中: 700
終了後: 900
結果が直前に呼ばれた関数やアプリの状態に依存しており、テストや再利用が難しくなります。
スコープのベストプラクティス
値は引数と戻り値でやり取りする
状態の受け渡しは引数と戻り値で行い、グローバルに依存しない関数にすると、安全でテストしやすくなります。
def add_points(current, delta):
# 入力と出力が明確な「純粋」な関数に近づける
new_total = current + delta
return new_total
total = 0
total = add_points(total, 10)
total = add_points(total, -3)
print("合計:", total)
合計: 7
変数の範囲は最小にする
必要な場所でだけ変数を作ると、名前の衝突や思わぬ上書きを防げます。
Pythonはブロックスコープ(ifやfor)を作りませんが、関数で囲うだけでスコープを限定できます。
def process(values):
result = []
for v in values:
doubled = v * 2 # 必要な最小の範囲でのみ使用
result.append(doubled)
return result
print(process([1, 2, 3]))
# print(doubled) # 関数外なので NameError
[2, 4, 6]
globalの使用は最小限にする
どうしても必要な設定や定数以外は、globalを避けるのが基本です。
代替として、設定や状態を引数で渡す方法があります。
コード例(設定を引数で渡す)
def apply_with_config(price, config):
# 状態は引数の config から受け取る
return int(price * config["rate"])
cfg = {"rate": 1.1}
print(apply_with_config(100, cfg))
cfg["rate"] = 1.2 # 値を変えたい時は呼び出し側で明示的に変更
print(apply_with_config(100, cfg))
110
120
どの値が使われるかが関数の引数に現れるため可読性が上がり、テストで差し替えも容易です。
globalは最小限にとどめましょう。
まとめ
Pythonでは「関数内で代入した名前はローカル」という大原則があり、グローバルを関数内で更新するにはglobal
が必須です。
ローカル変数は関数外から見えず、引数はローカルとして扱われます。
同名の変数がある場合、ローカルが優先され、思わぬUnboundLocalError
の原因になります。
設計としては引数と戻り値で値をやり取りし、変数の範囲を最小にし、globalの使用は最小限にすることで、読みやすく安全なコードを書けます。