Python♪関数で変なエラーが出た:local variable ‘x’ referenced before assignment

関数を使っていたら変なエラーが出てきました。「local variable ‘x’ referenced before assignment」なに、このエラー?そこで、グーグル翻訳さんでポン!「代入の前に参照されるローカル変数 ‘x’」なるほど!「代入前(定義される前)にローカル変数’x’が参照されている」ってことね。グーグル翻訳さんありがとう。

1. エラー発生のサンプルコード1

どうやら、コード01では、3行目のprint(x)でまだ定義されていないローカル変数xが使われているということらしいのです。私はxについて、3行目までは、6行目でグローバル変数として定義されているx=2の値をとり、4行目以降x=5になるのだと思っていました。ですから、3行目のprint(x)の出力は2になると思っていました。
しかし、どうやら、def test1()は関数内にx=5があるので、関数内では最初から変数xはローカル変数として扱われるようなのです。だから、print(x)って言っても、xがまだ定義されてないよ。とエラーで教えてくれたのです。

#コード01
def test1():
    print(x)
    x = 5
    
x = 2
test1()
#出力01
UnboundLocalError: local variable 'x' referenced before assignment

それならばと、3行目のprint(x)と4行目のx = 5を入れ替えてみました。このようにすれば、print(x)でxを使う前に、x = 5でxを定義するので大丈夫なはず。

#コード02
def test1():
    x = 5
    print(x)
    
x = 2
test1()
#出力02
5

今度はうまくいきました。Pythonでは、関数の中で途中まではグローバル変数を参照し、途中からローカル変数に定義しなおすことはできません。グローバル変数ならグローバル変数のまま。ローカル変数ならローカル変数のままなのです。注意しましょう。

2. エラー発生のサンプルコード2

しかし、分かっていたつもりなのに、また、やってしまいました。本当はもうちょっと長いコードだったんですが、シンプルに書き直したコード03を見てください。私は、xの値は最初は2で、2に5を加えるつもりだったのです。
「えっ、また同じエラー?」・・・しばらく考える・・・「あ~、そうか。なるほど・・・」

#コード03
def test1():
    x = x + 5 

x = 2
test1()
#出力03
UnboundLocalError: local variable 'x' referenced before assignment

コード03では、3行目のx = x + 5 では、「x =」の形でxを定義しているので、xは関数内でローカル変数として扱われます。つまり、関数内ではxはグローバル変数2ではないのです。これが重要ポイントです。
さて、もう一度、考え直してみます。3行目の「x = x +5」。これは、右辺と左辺がイコールであるという命令文ではありません。そもそも、xとx+5が同じになるなんてありえません。プログラミングの代入は、「右辺の」を計算し、「左辺の変数」に渡すという意味です。右辺が値で、左辺は値を渡す場所なのです。(参照だけ渡すとか、そういう話はちょっといったん、忘れてください。)つまり、x = x + 5は、まず「右辺の」を求めます。しかし、右辺のx + 5のxは、関数内で定義されたローカル変数なので、まだ、定義されておらず、値が決まっていません。だから、local variable ‘x’ referenced before assignment「代入前(定義される前)にローカル変数’x’が参照されている」ということなのです。

なんか、気をつけないと、他でも間違いそうですね。やっぱり、グローバル変数はできるだけ使わず、関数の外の値を参照するときは、引数を使うなど、明示した方が、エラーの少ないプログラミングができるように思います。まあ、好みかもしれませんけど。

3. 引数で変数を渡したときとの比較

一方、引数で変数を渡したときとの比較を行います。コード04では引数でaを関数に渡しています。コード01とは違い、4行目 (a = 5)で定義する前にprint文で出力してもエラーは発生しません。4行目のa = 5の前までは引数として参照渡しされた値(a=3)として参照することができます。そして、4行目のa = 5で、ローカル変数として変数の再定義を行った後は、aの値は5となります。この様に、引数を用いて関数内に変数を渡した場合は、グローバル変数を関数内で用いる場合とは異なり、途中で変数の再定義を行うことが可能です。

なお、その他の注意点としては、引数として「参照渡し」された変数aを、関数xxx()内の4行目で「再定義」した場合は、関数の外の変数aとは全く別の変数として定義されますので、関数の外と内ではお互い変更の影響を受けません。したがって、9行目の出力は3のままになっています。

#コード04
def xxx(a):
    print('関数の内部、変更前',a) #要素の変更
    a = 5
    print('関数の内部、変更後',a) #要素の変更
    
a = 3
xxx(a)
print('関数実行後、関数の外側:',a)
#出力04
関数の内部、変更前 3
関数の内部、変更後 5
関数実行後、関数の外側: 3

4. まとめ

(1) Pythonでは、関数の中で途中まではグローバル変数を参照し、途中からローカル変数に定義しなおすことはできません。ローカル変数として扱うときは、変数を使う前に変数を定義しましょう。

(2) グローバル変数の利用はできるだけひかえ、関数への入出力がわかりやすいプログラミングを心がけましょう。

(3) x = x + 1のような、プログラミングの代入は「右辺の」を計算し、「左辺の変数」に渡すという意味です。右辺がで、左辺は値を渡す場所です。
右辺の式を計算する前に、右辺で使用する変数を定義する必要があります。

(4) 引数を用いて関数内に変数を渡した場合は、グローバル変数を関数内で用いる場合とは異なり、途中で変数の再定義を行うことが可能です。

私が実際に購入した教材のご紹介

以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。

Python♪私が購入したPythonの書籍のレビュー

UdemyのPythonの動画講座を書籍を買う感覚で購入してみた