エラノート エラノート

変数のスコープとは?有効範囲をわかりやすく解説

スコープ 変数 Python JavaScript 概念解説
広告スペース (article-top)

プログラミングで「スコープ」という概念は、変数がどこから使えるか(有効範囲)を決めるルールです。スコープを理解していないと、「変数が見つからない」「思った値と違う」といったバグに悩まされます。この記事ではスコープの基本をPythonとJavaScriptの例で解説します。

スコープとは

スコープ(scope)は「変数が有効な範囲」のことです。ある場所で定義した変数が、別の場所からアクセスできるかどうかはスコープによって決まります。

大きく分けて2つのスコープがあります。

  • グローバルスコープ: プログラム全体からアクセスできる
  • ローカルスコープ: 特定のブロック(関数など)の中でだけアクセスできる
# グローバル変数
message = "こんにちは"

def greet():
    # ローカル変数
    name = "太郎"
    print(f"{message}{name}さん")  # messageにアクセスできる

greet()  # こんにちは、太郎さん
# print(name)  # NameError: nameはgreet()の中でしか使えない

この例では、messageはグローバルスコープに定義されているのでどこからでもアクセスできます。一方、namegreet()関数のローカルスコープに定義されているので、関数の外からはアクセスできません。

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にはvarletconstでスコープの挙動が異なります。

ブロックスコープ(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

letconstはブロック({}で囲まれた範囲)の中でだけ有効です。

関数スコープ(var)

if (true) {
  var z = 30;
}
console.log(z);  // 30(varはブロックスコープを持たない)

function example() {
  var local = "関数内";
}
// console.log(local);  // ReferenceError(関数スコープは有効)

varはブロックスコープを持たず、関数スコープのみです。これが予期せぬバグの原因になるため、現在はletconstが推奨されています。

ループでの注意点

// 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ではletconstを使い、varは避ける
  • Pythonではglobalの使用を避け、引数と戻り値でデータを渡す
  • 関数は小さく保ち、必要なデータは引数で受け取る

スコープを正しく理解すると、変数の衝突や予期せぬ値の変更といったバグを大幅に減らせます。

広告スペース (article-bottom)

あわせて読みたい