pythonの変数のスコープってどこからどこまで?

新しくプログラミング言語を始めると気になってくるのが変数のスコープです。

どこからどこまでが有効なのか知っていて損をすることはまずありません。

スコープとは

ご存知の方も多いとは思いますが、スコープとは変数の有効範囲の事です。

有効範囲、つまりその変数を利用できる範囲の事です。

pythonのスコープ

早速ですが、次のプログラムを実行すると画面には何が表示されるでしょう?

x = 1
print(x)

はい、当然1が表示されます。

では次はどうでしょう?

x = 1

def funcX():
    x = 2

print(x)

関数funcXの中でx2を代入しています。

関数を実行していないので当然表示される内容は1です。

続いて、関数funcXを実行した場合を考えます。

x = 1

def funcX():
    x = 2

funcX()
print(x)

xを表示させる前にfuncXを実行しています。
しかし、画面に表示されるのは1です。

ひょっとしてうまく代入出来なかったのかもしれません、funcXの中でxを表示させて確認してみましょう。

x = 1

def funcX():
    x = 2
    print("funcX : ",x)

funcX()
# funcX : 2
print(x)
# 1

funcXで表示させたxにはきちんと2が入っています。
でもやっぱり、funcXの外で表示させたx1のままです。

このことから分かるのはx = 1をした時のxfuncXの中でx = 2をした時のxは別物だということです。

同じ変数名でも場所によって中身が違う、それこそがスコープの注意するところです。

グローバルな変数 ローカルな変数

pythonのスコープは基本的にはクラスや関数単位です。

関数の中で定義した変数はその関数の中でしか使えません。

グローバル変数やローカル変数など耳にしたことがあると思います。
それぞれがどんなものなのか簡単に説明します。

グローバル変数

グローバル変数というのは何処からでも参照できる変数の事です。

関数やクラスの外で定義した変数が該当します。

先ほどのプログラムを例に挙げると

x = 1 # <- グローバル変数  

def funcX():
    x = 2
    print("funcX : ", x)

funcX()
print(x) # <- このxはグローバル変数x  

となります。

ローカル変数

関数やクラスの中で宣言された利用できる範囲が制限された変数のことをローカル変数といいます。
クラス定義の中で宣言された変数はクラス変数とも呼ばれます。

x = 1 # <- グローバル変数

def funcX():
    x = 2 # <-ローカル変数x
    print("funcX : ", x) # <- ここのxはローカル変数のx

funcX()
print(x) # <- このxはグローバル変数x

ローカル変数は原則、定義したスコープの外からは呼び出せないので、

x = 1

def funcY():
    y = 10

print(x)
# 1
print(y)
# NameError: name 'y' is not defined

となってしまいます。

また、以下のようにグローバル変数に関数の中で代入を行なって変更を加えても、関数の中で宣言された変数扱いになるので、

z = 0

def funcZ():
    z = 100

funcZ()
print(z)
# 0

グローバル変数zの方に影響はありません。
先ほどのxもその例です。

グローバル変数の参照

先ほど、グローバル変数はどこからでも参照できるという話をしましたが実際にどういうことかと言うと、こう言うことです。

x = 10

def funcY():
    y = x
    print("y =", y)

funcY()
# y = 10
print("x =", x)
# x = 10

関数funcYの中ではxを定義していませんが、yにはxの中の値10が代入されています。
これがグローバル変数は、どこからでも参照できると言うことです。

しかし、次のような場合は注意が必要です。

x = 10

def funcY():
    y = x
    print("y =", y)
    x = 100

funcY()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 2, in funcY
# UnboundLocalError: local variable 'x' referenced before assignment
print("x =", x)
# x = 10

ローカル変数xが代入される前に参照されたと言うエラーが出ました。

プログラムは、基本的に上から順番に実行されるのでfuncYy = xxはグローバル変数のxを指しそうなものですが、そうは問屋が卸しません。

そのあとの行に注目してください。
x = 100を実行しています。
つまり、ローカル変数xを定義しています。

Pythonは変数を探す時、呼び出されたところを基準としてスコープが小さいところを優先して探します。

スコープが小さいところが優先と言いましたが、自身より小さいスコープの変数は覗きに行きません。
自分を基準として、内側から外側に向かって変数を探しに行きます。

なのでPythonはy = xの時にfuncYの中を見渡してxがないかを探します。

すると、3行目にx = 100をしているのを見つけます。

なので、1行目のxはローカル変数のxとして判断されます。

しかし、ローカル変数xが定義されるのは、y = xよりも後です。

だから、xが代入される前に参照されたと言うエラーを吐くわけです。

変数の名前はスコープが小さいところのものが優先的に利用されると言うことを覚えておきましょう。

グローバル変数がどこからでも参照できると言うのは、スコープの小さいところから順番に探して行った時、最後に探される場所がグローバル空間だからと言う理由です。

x = 10

def funcY():
    y = x
    print("y =", y)

funcY()
# y = 10
print("x =", x)
# x = 10

上の例だと、変数を探しに行く順番が

  1. funcYの中
  2. グローバル空間

と言う順番になっていてfuncYのなかにxがないのでグローバル空間にxを探しに行きます。 すると、xが見つかるのでfuncYのなかのxはグローバル空間のxであると認識されるわけです。

グローバル宣言

関数の中でもグローバル変数の値を変更したいときはあると思います。

そんな時に利用できるのが、グローバル宣言です。

関数の中でglobal 変数名とすることで利用できます。

グローバル宣言をすることで、この関数の中の変数名はグローバル変数のことであるということを示すことができます。

つまり、関数などの中で加えた変更をグローバル空間の変数に反映させることができます。

x = 0 
y = 0
def funcX():
    global x
    x = 10
    y = 100

funcX()
print(x)
# 10 <- `funcX`の中でglobal宣言をしているので`x`に加えた変更が反映されている
print(y)
# y = 0 # <- `funcX`の中で`y`に加えた変更がは反映されない

ただ、この様なグローバル宣言をした変数をプログラムの中で多用すると、いつ どこで どのような 変更が加わったのかわかりにくくなってしまうので使い過ぎには気をつけましょう。

おまけ

どのタイミングでどの変数が使えるのか分からん。
という人は組み込み関数globalslocalsを使うとわかりやすかもしれません。

globals関数

グローバル空間で利用できる変数を変数名をキー、値をバリューとして持った辞書として返してくれます。

x = 10

def funcY():
    y = x
    print("y =", y)

print(globals()) # グローバル変数を取得  
# {'x': 10, 'funcY': <function funcY at 0x108707378>}

※本当は組み込みの関数などいろいろ表示されますが、見づくなるので省略しています。

locals関数

呼び出されたローカル空間で利用できる変数をglobals関数と同じように変数名をキー、値をバリューとして持った辞書を返してくれます。

x = 10

def funcY():
    y = 100
    print(locals()) # 呼び出されたローカル空間、つまり`funcY`の中で利用できるローカル変数を取得

funcY()
# {'y': 100}

最後に

変数のスコープが分かっていると、プログラムがぐっと書きやすくなります。
人のコードを読む時にも役に立つので、変数のスコープを気にしながらコードを書いてみましょう。