↓ メインコンテンツへスキップ
  1. 第03講 関数(2/2; 関数の応用)/

変数とスコープ

変数の種類とスコープ
#

教科書 p.197(Lesson 7.6)

関数を定義するようになると、関数の中で使う変数と、関数の外で使う変数が混在することがあります。 このうち、関数内で名前が決められ、利用される変数を ローカル変数(Local Variable) と呼びます。 関数の仮引数もローカル変数です。

一方、関数の外で名前が決められ、利用される変数を グローバル変数(Global Variable) と呼びます。 どちらも有効範囲があり、これを スコープ(Scope) と呼びます。 ローカル変数の有効範囲は、その変数が定義された関数内部であり、 グローバル変数の有効範囲は、その変数が定義されたファイル内全体です。

def add(a, b):      # a, b は仮引数で、ローカル変数。
    c = a + b       # c はローカル変数。
    return c

def subtract(a, b): # a, b は仮引数で、ローカル変数。
    c = a - b       # c はローカル変数だけど、addでのcとは別の変数。
    return c        # 関数が異なると、例え名前が同じでも、ローカル変数は全く異なる変数として扱われる。

def multiple(a, b): 
    d = a * b       # 関数の中からは、他の関数の変数(c)にはアクセスできない。
    return d        # a, b はたまたま同じ名前だが、他の関数の a, b とは全く別物。

def divide(a, b):
    if b == 0:
        return "0で割ることはできません。"
    d = a // b
    return d

x = 10         # これ以降の変数は関数に所属していないため、グローバル変数。
y = 5          # 関数の外なので、関数内で定義されたローカル変数(a, b, c, d)にはアクセスできない。
print(f"{x} + {y} = {add(x, y)}")
print(f"{x} - {y} = {subtract(x, y)}")
print(f"{x} * {y} = {multiple(x, y)}")
print(f"{x} // {y} = {divide(x, y)}")

このプログラムでは、add 関数、subtract 関数、multiple 関数、divide 関数の中で a, b という変数が使われています。 また、add 関数、subtract 関数では c、multiple 関数、divide 関数では d という変数が使われています。 これらの変数は、それぞれの関数内で定義されているため、ローカル変数です。 ローカル変数は関数内でしか有効ではなく、関数の外側では利用できません。

グローバル変数を利用する
#

一方、x と y は関数の外で定義されているため、グローバル変数です。 グローバル変数はファイル内のどこでも(関数内部でも)利用できます。 ただし、関数の中でグローバル変数を 読み取る のと、再代入する のとでは扱いが異なります。

  • 読み取るだけなら、何もしなくても関数内からグローバル変数を参照できます。
  • 関数内で a = 1 のように代入すると、その a は 新しいローカル変数 になります。同じ名前のグローバル変数があっても、それとは別物です。
  • 関数内からグローバル変数に再代入したいときは、global a と宣言します。この宣言があると、その関数内の a はグローバル変数 a を指します。
  • リストのように中身を変更できるオブジェクトの場合、items.append(x) のように 中身を変える 操作には global は不要です。名前 items を別のオブジェクトに付け替えるわけではないからです。
a = 0
items = []

def read_global():
    print(f"read_global: a = {a}")  # 読み取るだけなら global は不要。

def assign_local():
    a = 1                            # 関数内で代入すると、この a は新しいローカル変数になる(グローバル変数 a とは別物)。
    print(f"assign_local: a = {a}")

def assign_global():
    global a                         # グローバル変数 a に再代入することを宣言する。
    a = 10                           # global を書いたので、これはグローバル変数 a への代入。
    print(f"assign_global: a = {a}")

def append_item(x):
    items.append(x)                  # 名前 items を付け替えるのではなく、items が指すリストの中身を変えている。global は不要。
    print(f"append_item: items = {items}")

print(f"global a = {a}, items = {items}")
read_global()
assign_local()
print(f"global a = {a} after assign_local")
assign_global()
print(f"global a = {a} after assign_global")
append_item(5)
print(f"global items = {items} after append_item")

実行結果
#

$ python3 scope2.py
global a = 0, items = []
read_global: a = 0
assign_local: a = 1
global a = 0 after assign_local
assign_global: a = 10
global a = 10 after assign_global
append_item: items = [5]
global items = [5] after append_item

assign_local の中の a = 1 はグローバル変数 a を変更していない(after assign_local でも 0 のまま)のに対し、 global a を宣言した assign_global はグローバル変数 a を 10 に変えています。 また、append_item は global なしでグローバル変数 items が指すリストの中身を変えています。

なお、関数の外の変数を関数内から書き換えるプログラムは、どこで値が変わるのかが追いにくくなります。 関数には必要な値を引数で渡し、結果は返り値で受け取るのが基本です。global を使うのは、それでは書けない場合に限りましょう。

ローカル変数一覧
#

教科書 p.203 (Lesson 7.6)

dir関数は、関数内で利用すると、その関数で定義されているローカル変数一覧を取得できます。 デバッグ時などに利用すると便利です。

def func():
    a = 1
    b = 2
    c = 3
    print(f"func: {dir()}")

func()

実行結果
#

$ python3 scope3.py
func: ['a', 'b', 'c'] # func で定義されているローカル変数の一覧

変数の寿命
#

教科書 p.204 (Lesson 7.7)

Python の変数は、名前とオブジェクト(値)を結びつけたものです。 「変数の寿命」を考えるときは、名前がいつまで使えるか(スコープ) と、オブジェクトがいつまで存在するか を分けて考える必要があります。

ローカル変数の名前は、関数の中で代入された時点から使えるようになり、関数が終了すると使えなくなります。 ただし、その名前が指していたオブジェクトまで消えるとは限りません。 関数が return で返したオブジェクトや、他の変数からも参照されているオブジェクトは、関数が終わった後も使い続けられます。 どこからも参照されなくなったオブジェクトは、Python が自動的にメモリから解放します。

グローバル変数の名前は、プログラムの開始前にまとめて用意されるのではなく、ファイルの先頭から順に実行されていく中で、代入文が実行された時点で使えるようになります。 そのため、関数の中でグローバル変数を参照する場合は、その関数が 呼び出される時点 でその名前に値が代入されていれば利用できます。

def make_list(n):
    result = []                   # result はローカル変数。
    for i in range(n):
        result.append(i * i)
    return result                 # result が指しているリストを返す。

squares = make_list(5)            # 関数は終了し、名前 result はもう使えないが、
print(squares)                    # リストそのものは squares という名前で使い続けられる。

def show():
    print(f"show: total = {total}")  # total は関数の呼び出し時に探される。

total = 100                       # ここで初めて名前 total が値に結びつく。
show()                            # この時点では total が存在するので使える。

実行結果
#

$ python3 scope4.py
[0, 1, 4, 9, 16]
show: total = 100

make_list の中のローカル変数 result は、関数が終わると名前としては使えなくなりますが、 result が指していたリストは squares という名前で受け取っているので、そのまま使えます。 一方、show の呼び出しを total = 100 より前に移動すると、その時点では名前 total がまだ存在しないため、NameError: name 'total' is not defined となります。