Pythonのスコープとは?|ローカル変数とグローバル変数の違い

ソフトウェア

はじめに

Pythonでは、関数の中で定義した変数を、関数の外から使おうとするとエラーになることがあります。一方、関数の外で定義した変数は、関数の中から参照できる場合があります。

例えば、次のコードを見てください。

def show_value():
    x = 10
    print(x)

show_value()
print(x)

実行すると、関数内では 10 が表示されますが、関数外の print(x) でエラーになります。

10
NameError: name 'x' is not defined

※エラー表示は、原因となった行などを省略しています。

なぜ、同じ名前の x なのに、関数の中と外で使える場所が違うのでしょうか。この違いを理解する鍵が、スコープ(scope)です。

スコープとは、プログラムの中で名前を参照できる範囲のことです。Pythonでは、名前がどのスコープに属するかによって、その名前を参照できる場所が変わります。

この記事では、前回学んだ「変数と参照」の考え方を使いながら、ローカル変数とグローバル変数の違いを、コードと図で確認します。

この記事でわかること

この記事では、次の内容の理解を目指します。

  • Pythonのスコープとは何か
  • ローカル変数とグローバル変数の違い
  • 関数内で定義した変数を外から使えない理由
  • 関数外の変数を関数内から参照できる理由
  • 同じ名前の変数が関数内外にあるときの動作
  • 関数内の代入で UnboundLocalError が起こる理由

1.Pythonのスコープとは何か?

Pythonでは、変数はオブジェクトを参照する名前として扱われます。しかし、ある場所で名前を定義したからといって、プログラムのどこからでもその名前を使えるわけではありません。

例えば、次のコードを見てください。

def greet():
    message = "Hello"
    print(message)

greet()

実行結果は次のようになります。

Hello

message は関数 greet() の中で定義されています。この名前は関数内では参照できますが、関数外から直接参照することはできません。

def greet():
    message = "Hello"

greet()
print(message)

実行すると、次のエラーが発生します。

NameError: name 'message' is not defined

これは、関数内で定義された名前 message が、関数外から名前を探すときの対象にならないためです。

この関係を図で確認してみましょう。

ローカルスコープとグローバルスコープ

上の図では、message という名前が関数のローカルスコープに属しています。そのため、関数内では参照できますが、関数外から同じ名前を直接参照することはできません。

なお、この図は名前を参照できる範囲を模式的に表したものであり、実際のメモリ配置を示したものではありません。

ここで重要なのは、名前を参照できる範囲と、オブジェクトが存在する範囲は同じではないという点です。

例えば、関数がオブジェクトを戻り値として返せば、そのオブジェクトを関数外から利用できます。

def greet():
    message = "Hello"
    return message

result = greet()
print(result)

実行結果は次のようになります。

Hello

この場合、関数外から message を直接参照しているわけではありません。関数が返したオブジェクトを、関数外の result という名前で参照しています。

スコープが制限するのは名前の参照であり、オブジェクトを関数の外へ渡すことではありません。

2.ローカル変数とは何か?

関数のローカルスコープに属する変数を、ローカル変数と呼びます。

例えば、次のコードを見てください。

def calculate():
    x = 10
    y = 20
    result = x + y
    print(result)

calculate()

実行結果は次のようになります。

30

x、y、result は関数内で使われるローカル変数です。これらの名前は、通常、関数の外から直接参照できません。

2-1.引数もローカル変数として扱われる

前回の記事では、関数に引数を渡すと、関数内の引数名が渡されたオブジェクトを参照することを確認しました。

例えば、次のコードです。

def show_number(x):
    y = x * 2
    print(y)

show_number(10)
show_number(20)

実行結果は次のようになります。

20
40

この例では、引数名 x も、関数内で定義した y もローカル変数です。

最初の呼び出しでは x が整数オブジェクト 10 を参照し、次の呼び出しでは、その呼び出しに対応する x が整数オブジェクト 20 を参照します。

関数を呼び出すたびに、その呼び出しに対応するローカルな名前の束縛が用意されます。つまり、関数の引数も、関数内で使われるローカルな名前の1つです。

なお、関数の呼び出しが終了しても、そこで使われたオブジェクトが必ず消えるわけではありません。ほかの場所から参照されているオブジェクトは、引き続き利用できます。

3.グローバル変数とは何か?

モジュールのトップレベルで定義された変数を、グローバル変数と呼びます。

例えば、次のコードを見てください。

message = "Hello"

def greet():
    print(message)

greet()

実行結果は次のようになります。

Hello

message は関数の外で定義されたグローバル変数です。この例では、関数内に message というローカル変数がありません。そのため、Pythonはグローバルスコープにある message を見つけ、その名前が参照するオブジェクトを利用します。

この関係を図で確認してみましょう。

関数内からグローバル変数を参照する

上の図では、関数内の print(message) が、グローバルスコープにある message を参照しています。関数内に新しいローカル変数 message が作られたという意味ではありません。

ローカルスコープに対象の名前がない場合、外側のスコープにある名前を参照できることがあります。

ただし、Pythonにはグローバルスコープ以外にも名前を探す場所があります。その探索順序については、次回の「LEGBルール」で詳しく解説します。

3-1.グローバル変数はモジュール単位で管理される

ここで注意したいのは、Pythonのグローバル変数は、厳密にはモジュール単位で管理されるという点です。

例えば、あるPythonファイルのトップレベルで定義した名前は、そのモジュールのグローバルスコープに属します。別のモジュールにも、同じ名前のグローバル変数を定義できます。

したがって、グローバル変数は「プログラム全体で必ず1つだけ存在する変数」という意味ではありません。

4.関数内と関数外に同じ名前があるとどうなる?

次のコードを見てください。

x = 10

def show_value():
    x = 20
    print("関数内:", x)

show_value()
print("関数外:", x)

実行結果は次のようになります。

関数内: 20
関数外: 10

関数内では 20、関数外では 10 が表示されました。なぜでしょうか。

関数内の x = 20 はローカル変数 x への代入です。一方、関数外の x = 10 はグローバル変数 x への代入です。

同じ x という名前でも、異なるスコープに属しているため、別々の名前として扱われます。

この関係を図で確認してみましょう。

同じ名前のローカル変数とグローバル変数

上の図では、グローバルスコープの x とローカルスコープの x が、それぞれ別の整数オブジェクトを参照しています。

関数内で x を参照するとローカル変数 x が使われ、関数外ではグローバル変数 x が使われます。

このように、内側のスコープに同じ名前があることで、外側の名前が隠されることをシャドーイング(shadowing)と呼びます。ただし、外側の変数が削除されたり、参照先のオブジェクトが変更されたりするわけではありません。

同じ名前であっても、属するスコープが異なれば、別の名前の束縛として扱われます。

5.関数内でグローバル変数に代入しようとするとどうなる?

ここまでを見ると、関数内からグローバル変数を参照できるなら、その名前に代入することもできそうに思えるかもしれません。

しかし、次のコードはエラーになります。

x = 10

def change_value():
    x = x + 1
    print(x)

change_value()

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

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

※エラー表示は、原因となった行などを省略しています。

関数外には x = 10 があるのに、なぜエラーになるのでしょうか。

5-1.Pythonは関数内の代入を見てローカル変数を判断する

Pythonでは、関数内に名前への代入がある場合、global や nonlocal などの宣言がなければ、その名前は原則としてローカル変数として扱われます。

先ほどのコードには、次の代入があります。

x = x + 1

この文では、左辺の x に代入しています。そのため、Pythonは関数のコードを解析する段階で、この x をローカル変数として扱うことを決めます。

しかし、代入文を実行するときは、右辺の x + 1 を先に評価する必要があります。この時点では、ローカル変数 x にまだ値が束縛されていません。そのため、UnboundLocalError が発生します。

処理の流れを整理すると、次のようになります。

  1. 関数内に x への代入があるため、x はローカル変数として扱われる
  2. x = x + 1 を実行すると、右辺の x + 1 が先に評価される
  3. ローカル変数 x には、まだ値が束縛されていない
  4. UnboundLocalError が発生する

ここで重要なのは、Pythonがグローバル変数 x を探しに行って失敗したわけではないという点です。関数内の x がローカル変数として扱われ、値を束縛する前に参照したことがエラーの原因です。

5-2.必要な値を引数として渡す方法

このような場合、必要な値を引数として渡し、結果を戻り値で受け取る方法があります。

def increment(x):
    return x + 1

x = 10
x = increment(x)

print(x)

実行結果は次のようになります。

11

このコードでは、関数 increment() が引数 x を受け取り、x + 1 の結果を返します。関数外では、戻り値をグローバル変数 x に再代入しています。

この方法なら、関数内でグローバル変数を直接再代入する必要がありません。

なお、Pythonには関数内からグローバル変数への代入を可能にする global という構文もあります。ただし、その詳しい仕組みは別の記事「Pythonのglobalとnonlocalの違いとは?|名前の束縛を変更する仕組み」で解説します。

6.ローカル変数とグローバル変数の違いを整理する

ここまでの内容を表にまとめます。

比較項目 ローカル変数 グローバル変数
主な定義場所 関数内 モジュールのトップレベル
属するスコープ ローカルスコープ グローバルスコープ
関数内からの参照 値が束縛されていればできる 同名のローカル変数などがなければできる
関数外からの直接参照 通常はできない 同じモジュール内でできる
関数内からの再代入 通常の代入でできる global 宣言が必要

重要なのは、ローカル変数とグローバル変数が、異なる種類のオブジェクトを表しているわけではないことです。

どちらも、オブジェクトを参照する名前です。違うのは、その名前がどのスコープに属し、どこから参照できるかという点です。

7.スコープを意識すると何がよいのか?

スコープを理解すると、関数の動作を予測しやすくなります。特に、関数が外側の変数に依存しているかどうかを意識することが大切です。

例えば、次の関数を見てください。

tax_rate = 0.1

def calculate_tax(price):
    return price * tax_rate

print(calculate_tax(1000))

実行結果は次のようになります。

100.0

この関数は、引数 price だけでなく、外側で定義された tax_rate にも依存しています。

一方、次のように書くこともできます。

def calculate_tax(price, tax_rate):
    return price * tax_rate

print(calculate_tax(1000, 0.1))

実行結果は同じです。

100.0

後者では、計算に必要な値が引数として明示されています。このようにすると、関数がどの値を使って計算するのかがわかりやすくなり、テストや再利用もしやすくなります。

もちろん、グローバル変数を使うことが常に悪いわけではありません。ただし、変更される可能性のある外部の状態に関数が強く依存すると、動作を追いにくくなる場合があります。

関数に必要な値を引数として渡し、結果を戻り値で受け取る設計は、意図しない依存関係を減らす基本的な方法の1つです。

まとめ

今回は、Pythonのスコープと、ローカル変数・グローバル変数の違いを確認しました。重要なポイントを整理します。

  • スコープとは、名前を参照できる範囲のこと
  • 関数内のローカル変数は、通常、関数外から直接参照できない
  • 関数の引数もローカル変数として扱われる
  • 関数内からグローバル変数を参照できる場合がある
  • 同じ名前でも、異なるスコープに属していれば別の名前として扱われる
  • 関数内に代入がある名前は、原則としてローカル変数として扱われる
  • 必要な値を引数で受け取り、戻り値を返すと、外部への依存を減らしやすい

Pythonのスコープを理解する鍵は、「オブジェクトの参照」と「名前を探す場所」を区別することです。

前回の記事では、関数に引数を渡したとき、関数内の引数名が同じオブジェクトを参照することを確認しました。今回の記事では、さらに、その名前がどの範囲で使えるのかを確認しました。

次回は、Pythonが名前を探す順序を決める「LEGBルール」を解説します。ローカルスコープとグローバルスコープに加えて、関数を入れ子にした場合のスコープや組み込みの名前を扱います。

関連記事

▶ 前回:Pythonの関数の引数はどう渡される?|変数と参照から理解する
Pythonの関数の引数はどう渡される?|変数と参照から理解する
Pythonの関数の引数は値渡し?参照渡し?変数とオブジェクトの参照関係を図とコードで解説。整数を再代入しても呼び出し元が変わらず、リストを変更すると影響する理由を、Pythonの仕組みから理解します。
▶ 次回:PythonのLEGBルールとは?|変数はどこから探されるのか

(準備中)

Part 2:Pythonの関数とスコープをまとめて理解する

▶ Pythonの関数とスコープを理解する|引数・LEGB・クロージャ

(準備中)

関連書籍

Pythonのスコープや関数の仕組みをさらに学びたい人には『入門 Python 3』もおすすめです。基本的な文法から関数、名前空間などを確認したいときに役立ちます。

Amazon.co.jp

コメント

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