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文で出力してもエラーは発生しません。

引数を用いて関数内に変数を渡した場合は、関数に入った時点で変数には値(参照)が代入されています。つまり、その変数を関数内で定義することなく使用することができます。

したがって、コード04では3行目までは引数として参照渡しされた値(a=3)として参照することができます。つぎに、4行目のa = 5ではaがローカル変数として再定義されるので、関数内では4行目以降のaの値は5となります。

なお、4行目でaはローカル変数として再定義されているので、関数内での変更は関数の外に影響を与えず、関数の外の9行目では3が出力されます。

引数で変数を渡した場合には、関数内にローカル変数として定義した記述があったとしても、最初から変数に値(参照)が代入された状態になります。したがって、コード01~03のときのように変数を定義しなければ使えないといったことはありません。

#コード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の動画講座を書籍を買う感覚で購入してみた

その他

Twitterへのリンクです。SNSもはじめました♪

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?