Pythonで、def や lambda を使って、関数を定義する際、Pythonが変数をどのように扱っているのかを理解してくことは重要です。これを知らないと、変数によるエラーが発生してしまい、その原因を突き止めるのに苦労してしまいます。
そこで、ここでは、そのような事態を回避するために重要な知識である、
- Pythonの変数スコープとは何か?
- ローカル変数を扱うときの注意点
- グローバル変数を扱うときの注意点
- グローバル・ステートメントとは何かとその使い方
について解説します。
1. Pythonのスコープとは
スコープとは、端的に説明すると「変数の有効範囲」のことです。
Pythonで変数を作った時、それはネームスペースという場所に保管されます。変数を使う時は、そのネームスペースに保管された値を引っ張ってきます。そして、ある変数が、コードの中のどこで作られたかによって、その有効範囲(スコープ)が違ってきます。
文字だけの説明だと、なかなか理解できないですよね。
実際のコードとともに見ていきましょう。以下をご覧ください。なお、 def 文については「Pythonのdef文を使った関数の作り方まとめ」でご確認ください。
v = 25
def printer():
v = 50
return v
このコードでは、変数 v に 2 回、値が代入されています。
最初に v = 25 としており、その後、 def ブロックの中で v = 50 としています。つまり同じ変数 v に2つの異なる値が設定されているということですね。
さて、ここで問題です。
- print(v)を実行すると、どちらの数値が表示されるでしょうか?
- pirnter 関数を実行すると、どちらの数値が表示されるでしょうか?
少し考えてみてください。
それでは実際に確認してみましょう。まず print(v)を実行してみます。
print(v)
次に、printer 関数を実行してみます。
printer()
興味深いことに、同じ変数 v に対して処理を実行しているのに、表示される値が異なっています。これはどういうことでしょうか?
別の例を見ながら理解を深めていきましょう。
次のコードをご覧ください。これは、ほとんど上のと同じものです。違いは、def文のブロックの外側で、変数 x が定義されていないという点だけです。
def printer():
v = 50
return v
この時 print(v) を実行するとどうなるでしょうか?
確認してみましょう。
print(v)
今度は、エラーが発生してしまいました。エラーの内容は NameError です。「 “name ‘v’ is not defined” 変数 v は定義されていません」と表示されていますね。
ちなみに、この時でも、printer 関数を実行すると変わらず 50 と出力されます。
なぜ、このようなことが起きるのでしょうか?
その原因こそ、「スコープ」という概念にあります。そして、スコープを理解しておくことは、変数に値を適切に代入し、その値を呼び出すためにとても重要になっていきます。
それでは、次からスコープについて詳しく解説していきます。
2. Pythonの4つのスコープ(LEGBルール)
Pythonの変数のスコープには、4つのタイプがあります。
- Local(ローカル変数):
関数(defやlambda)に代入された変数で、グローバルに宣言されていないもの。 - Enclosign Local(エンクロージング関数ローカル変数):
内側から外側までの、あらゆるエンクロージング関数(defやlambda)の変数名。 - Global(グローバル変数):
ファイルのトップレベルに代入された変数名で、ファイルの中でグローバルに宣言されているもの。 - Built-in(組み込み関数の変数名):
組み込み関数にあらかじめ代入されている変数名。print, range, SyntaxErrorなど。
それぞれ、簡単に例を挙げておきます。
なお、ローカル変数と、エンクロージング関数ローカルは、ほとんど同じと考えて差し支えないので、一緒に解説します。
2.1. ローカル変数(エンクロージング関数ローカル)
def 文や lambda 文などのブロックの中で、定義された変数は「ローカル変数」といって、その関数の中だけで機能します。
以下のコードをご覧ください。この中の変数 x は、lambda(ラムダ式)のブロックの中にあるため、ローカル変数です。なお lambda については、「Pythonのlambda(ラムダ式)の書き方まとめ」でご確認ください。
#この x はローカル変数です。
number = lambda x : 5
ローカル変数は、そのブロックの中のみの変数のため、print(x) で呼び出すことはできません。
print(x)
もちろん、lambda で作成した number 関数自体は問題なく使うことができます。
number(x)
同様に、 def 文の中の変数は、エンクロージング関数ローカルともいいます。「def文に囲まれている変数」という意味です。
次の例をご覧ください。
#def文でも同様です。
def integer() :
x = 5
return(x)
print(x)
こちらも print(x) を実行しようとすると NameError になります。もちろん integer 関数自体は使うことができます。
integer()
このように、def 文や lambda 式のブロックの中で定義した変数は、それらで作った関数の中でのみ使うことができます。言い換えると、ローカル変数のスコープ(有効範囲)は、そのブロック内のみということです。
2.2. グローバル変数
次に、グローバル変数について見てみましょう。
グローバル変数は、ファイルのトップレベルで定義されている変数のことです。トップレベルで定義されているものは「グローバルに宣言されている変数」ともいいます。
以下のコードをご覧ください。変数 name は、トップレベルで定義されたグローバル変数です。そのため print(name)を実行すると、NameErrorにはならず、値が呼び出されます。
name ="This is global"
def greeting():
name = "Mike"
return("Hello " + name)
print(name)
このようにグローバル変数のスコープ(有効範囲)は、ファイル全体です。
2.3. 組み込み関数の変数名
len 関数や、range 関数など、Pythonの組み込み関数に用いられている変数名を出力すると、次のように表示されます。
#組み込み関数
len
組み込み関数で使われている名前を、別の変数の名前に使ってしまうと、組み込み関数を使うときにエラーになってしまうので注意しましょう。
例えば、以下のようになります。
まず、同一名の変数がない場合は、関数が正常に機能します。
string = "this is a string"
len(string)
次に len に値を代入してみます。
len = 2
len
これによって、len 関数と、変数 len という同一の名前をもつオブジェクトができてしまいました。この状況で、もう一度 len 関数を実行してみましょう。
len(string)
このように、組み込み関数の名前と同じ名前のグローバル変数を作ってしまったため、元々の len 関数が使えなくなってしまいました。
こうした不具合を防ぐために、組み込み関数と被りそうな変数名を使うときは、先に print(関数名) を実行して確認しておきましょう。「Pythonの関数の使い方と一覧まとめ」でも、組み込み関数の一覧表を用意していますので、それも確認してみてください。
ただし、ローカル変数は関数ブロック内のみで有効な変数なので、あまり名前被りを気にする必要はありません。
3. ローカル変数の注意点
ローカル変数について、よくあるミスが、
- グローバル変数は、関数ブロックの中にも影響を与える。
- 関数ブロック内で定義したローカル変数は、関数ブロックの外では使えない。
というルールを忘れてしまった上で、コードを書いてしまうというものです。
繰り返しですが、同じ名前の変数があったとしても、「関数ブロック(def/lambda)の中で定義した変数」と、「ブロックの外で定義した変数」は別物です。前者は、その関数の中だけのものです。一方、ブロックの外で定義されているグローバル変数は、ブロックの中にも影響します。
それぞれ見ていきましょう。
3.1. グローバル変数は関数ブロックの中にも影響を与える
次のコードをご覧下さい。
x = 50
def func(x):
print("変数xは、", x) #最初のprint()ではグローバル変数が出力されます。
x = 2 #ここでローカル変数xを定義しています。
print("ローカル変数のxは、", x)
func(x)を実行してみます。
func(x)
print(x)を実行してみます。
print(x)
ご覧のように、func(x)を実行すると、最初の x は、1行目のグローバル変数 x = 50 を引っ張っています。その次にdefブロックの中で x に 2 を代入しました。そのため、「ローカル変数のxは、2」と出力されます。
しかし x = 2 は、このdefブロック内に限定されている変数です。つまり、ブロックの外側のグローバル変数 x の値は影響を受けません。そのため、最後のprint()関数で出力した x は、メインブロックの値 50 を引っ張っています。
このように、defブロックの中で定義されたローカル変数は、そのブロックの外には、何ら影響を及ぼすことはありません。
3.2. ローカル変数は関数ブロックの外では呼び出せない
同じ理由で、ブロックの中で定義した変数を、ブロックの外側で呼び出そうとしてもエラーになります。
以下をご覧ください。
def calc():
v = 2
ans = v * 3
print(ans)
calc()関数を実行してみます。
calc()
6ですね。それでは変数vを出力するとどうなるでしょうか?
print(v)
このように、”name ‘v’ is not defined”(x は定義されていません。)となり、NameErrorになります。
繰り返しですが、あるブロック内で作った変数は、そのブロックの中だけで有効なものなので、グローバルでは未定義なのです。
3.3. ローカル変数をグローバルに使うにはグローバルステートメントを使う
あるブロックの中で定義されたローカル変数は、そのブロックの外には、何ら影響を及ぼさないと説明しました。
ただし、グローバル・ステートメントを書いた場合は、その限りではありません。
早速見ていきましょう。
x = 50
def func():
global x
print("この関数は、今、グローバルxを使っています。")
print("なぜなら、グローバルxは",x,"だからです。")
x = 2
print("func()関数を実行したので、グローバルxの値は", x ,"に変わっています。")
print("func()を実行する前のxは", x, "です。")
func()
print("つまり、func()ブロックの外のxの値も", x, "になっています。")
このコードでは、グローバル・ステートメント(global x)で、この x はグローバル変数であることを宣言しています。そのため、関数内のブロックで、xに値を代入すると、その変更は、ブロックの外側のxにも反映されます。
なお、global x, global y, というように1つの関数内で、複数のグローバル・ステートメントを書くことができます。
また、グローバル・ステートメントがなければ、他者がプログラムを読む時に、その変数が、どこで定義されているのかの判別ができなくなり、不明瞭なコードになってしまいます。グローバル・ステートメントを使うと、その変数が、最も外側のグローバル変数から取っていることを、明確に伝えることができるという効果もあります。
ただし、ブロック内で、グローバル・ステートメントを使った場合は、グローバル変数の値そのものにも変更が反映されるため気をつけましょう。
4. グローバル変数の注意点
グローバル変数についても、1つ注意点があります。
以下をご覧ください。
一見すると、何の間違いのないコードに見えますが、実行すると、UnboundLocalErrorになります。
v = 2
def calc():
v = v * 10 #ここがエラーになります。
ans = 3 * v
print(ans)
#実行して見ましょう。
calc()
local variable ‘v’ referenced before assignment とありますね。これは、「ローカル変数の v が、値を代入される前に、参照されています」という意味です。
どういうことでしょうか?
まず、最初に v = 2 とグローバル変数を宣言していますね。その後にdefブロックの中で v = v * 10 と、vを再定義しています。もし、グローバル変数の値が生きているなら、 2 = 2 * 10(20) となっておかしな式になってしまいます。
グローバル変数が生きていなかったとしても v = v * 10 とすると、「そもそも v は何だ?」と混乱してしまいます。
そのため以下のようにするとエラーを防ぐことができます。
v = 2
def calc():
v_calc = v * 10 #ここがエラーになります。
ans = 3 * v_calc
print(ans)
#実行して見ましょう。
calc()
まとめ
いかがだったでしょうか?
初見では難しく感じるかもしれませんね。まず、変数名をつける時は、組み込み関数名と被っていないかを、実際にその名前を出力してみて確認する癖をつけておくと良いでしょう。
次に、ローカル変数は、グローバル・ステートメントを使って、それを実行しなければ、そのブロックの外には影響を及ぼさないことを覚えておきましょう。
最後に、グローバル変数を、関数ブロックの中で使うときは、記事中で示したような錯誤が起きないように意識付けておきましょう。
最初は、ミスを繰り返すこともあると思いますが、変数関係でエラーが起きた時は、まずここで説明したような変数スコープに関して思い出せるようにしておくと良いでしょう。
コメント