変数のスコープとは?有効範囲をわかりやすく解説
プログラミングで「スコープ」という概念は、変数がどこから使えるか(有効範囲)を決めるルールです。スコープを理解していないと、「変数が見つからない」「思った値と違う」といったバグに悩まされます。この記事ではスコープの基本をPythonとJavaScriptの例で解説します。
スコープとは
スコープ(scope)は「変数が有効な範囲」のことです。ある場所で定義した変数が、別の場所からアクセスできるかどうかはスコープによって決まります。
大きく分けて2つのスコープがあります。
- グローバルスコープ: プログラム全体からアクセスできる
- ローカルスコープ: 特定のブロック(関数など)の中でだけアクセスできる
# グローバル変数
message = "こんにちは"
def greet():
# ローカル変数
name = "太郎"
print(f"{message}、{name}さん") # messageにアクセスできる
greet() # こんにちは、太郎さん
# print(name) # NameError: nameはgreet()の中でしか使えない
この例では、messageはグローバルスコープに定義されているのでどこからでもアクセスできます。一方、nameはgreet()関数のローカルスコープに定義されているので、関数の外からはアクセスできません。
Pythonのスコープ
Pythonにはスコープの優先順位を表す「LEGB ルール」があります。
LEGBルール
変数を参照するとき、Pythonは以下の順序で探します。
L - Local(ローカル): 現在の関数内
E - Enclosing(外側の関数): 外側の関数内(ネストした関数の場合)
G - Global(グローバル): モジュールの最上位
B - Built-in(組み込み): Pythonの組み込み名前空間(print, lenなど)
x = "グローバル"
def outer():
x = "外側の関数"
def inner():
x = "内側の関数"
print(x) # "内側の関数"(Local)
inner()
print(x) # "外側の関数"(このスコープのx)
outer()
print(x) # "グローバル"(グローバルスコープのx)
それぞれのprint(x)は、自分のスコープにあるxを参照しています。
グローバル変数の変更
関数内からグローバル変数の値を変更するにはglobalキーワードが必要です。
count = 0
def increment():
global count # グローバル変数を使うことを宣言
count += 1
increment()
increment()
print(count) # 2
# globalなしだとエラーになる
count = 0
def increment():
count += 1 # UnboundLocalError
# Pythonは代入がある変数をローカル変数と解釈する
# ローカルのcountはまだ定義されていないのでエラー
# increment() # エラー
ただし、globalの多用はコードを複雑にするため推奨されません。関数の引数と戻り値でデータを受け渡す方が安全です。
# global の代わりに引数と戻り値を使う(推奨)
def increment(count):
return count + 1
count = 0
count = increment(count)
count = increment(count)
print(count) # 2
JavaScriptのスコープ
JavaScriptにはvar、let、constでスコープの挙動が異なります。
ブロックスコープ(let / const)
if (true) {
let x = 10;
const y = 20;
console.log(x); // 10
console.log(y); // 20
}
// console.log(x); // ReferenceError: x is not defined
// console.log(y); // ReferenceError: y is not defined
letとconstはブロック({}で囲まれた範囲)の中でだけ有効です。
関数スコープ(var)
if (true) {
var z = 30;
}
console.log(z); // 30(varはブロックスコープを持たない)
function example() {
var local = "関数内";
}
// console.log(local); // ReferenceError(関数スコープは有効)
varはブロックスコープを持たず、関数スコープのみです。これが予期せぬバグの原因になるため、現在はletとconstが推奨されています。
ループでの注意点
// NG: varはブロックスコープを持たない
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
// 出力: 3, 3, 3(すべて3になる)
// OK: letはブロックスコープを持つ
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
// 出力: 0, 1, 2(期待通り)
varを使うとループ変数がすべて同じ変数を参照してしまいます。letを使えばループの各反復で新しいスコープが作られます。
クロージャ
クロージャは、外側の関数のスコープにある変数を内側の関数が「覚えている」仕組みです。
Pythonの例
def make_counter():
count = 0
def counter():
nonlocal count # 外側の関数の変数を変更する
count += 1
return count
return counter
my_counter = make_counter()
print(my_counter()) # 1
print(my_counter()) # 2
print(my_counter()) # 3
JavaScriptの例
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const myCounter = makeCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3
クロージャを使うと、外部から直接アクセスできないプライベートな変数を実現できます。
よくある間違いと対策
間違い1: ローカル変数とグローバル変数の混同
# NG: 意図せずローカル変数を作ってしまう
total = 100
def add_tax():
total = total * 1.1 # UnboundLocalError
return total
# OK: 引数で受け取る
def add_tax(amount):
return amount * 1.1
result = add_tax(total)
print(result) # 110.0
間違い2: JavaScriptでvarを使ってしまう
// NG: varはスコープが広すぎる
for (var i = 0; i < 5; i++) {
// 処理
}
console.log(i); // 5(ループの外からアクセスできてしまう)
// OK: letを使う
for (let j = 0; j < 5; j++) {
// 処理
}
// console.log(j); // ReferenceError
スコープを正しく扱うためのポイント
- 変数はできるだけ狭いスコープで宣言する
- グローバル変数は最小限に抑える
- JavaScriptでは
letとconstを使い、varは避ける - Pythonでは
globalの使用を避け、引数と戻り値でデータを渡す - 関数は小さく保ち、必要なデータは引数で受け取る
スコープを正しく理解すると、変数の衝突や予期せぬ値の変更といったバグを大幅に減らせます。