グローバル変数は関数内からも参照することができます。では、関数内からグローバル変数を変更したときには、関数の外側にどの様な影響をあたえるのでしょうか。実はグローバル変数は、関数内へは「参照渡し」で渡されます。これは、関数の引数を用いて関数の外から関数に値を渡すのと全く同じ仕組みです。つまり、関数内部での変更が、関数の外側に与える影響は、「参照渡し」を理解していれば、全く同じなのです。
なお、この記事では、数値とリストの説明しかしていませんが、以下、参考記事(1)には、タプルやディクショナリなど、他の型の場合のことも説明しています。
参考記事:
(1) 「参照渡し後の要素の変更」と「参照渡し後の再定義」の違い
(2) 関数に引数で渡された変数の変更が、関数の外側に与える影響
関数への参照渡し後のリストの要素の変更
実際にコード01で試してみます。5行目のa[0] = 5で要素の変更を行うと、関数の外の値が変わっています。このように、グローバル変数は関数内側に「参照渡し」で渡されるため、関数内での要素の変更は、外側の関数にも影響を与えます。
#コード01
a = [3]
def xxx():
a[0] = 5 #要素の変更
print('関数実行前:a =', a)
xxx()
print('関数実行後:a =', a)
#出力01
関数実行前:a = [3]
関数実行後:a = [5]
これを説明すると下図のようになります。変数aは、まずグローバル変数としてa = [3]と定義されます。これは、関数xxx()を定義した段階で、数内にも同名の変数xがあれば、グローバル変数aから「参照渡し」により、関数内に変数aを渡されます。「参照渡し」ですので、同じ場所に記憶された[3]を共有します。次に、関数内でa[0] = 5により、関数内の変数aの要素を5に変更すると、関数の外でも連動して変更されます。
関数への参照渡し後の数値の再定義
次にグローバル変数の数値を関数内で変更した場合の関数外部への影響について確認します。コード02では、5行目のa = 5に注目してください。これは左辺が「変数名 =」となっていますので、要素の変更ではなく、「変数の再定義」です。つまり、ローカル変数として「変数の再定義」を行っているのです。再定義を行った時点で、外側のグローバル変数とは独立した別の変数になっています。したがって、関数内での変更が外側の関数に影響を与えません。
#コード02
a = 3
def xxx():
a = 5 #変数の再定義
print('関数実行前:a =', a)
xxx()
print('関数実行後:a =', a)
#出力02
関数実行前:a = 3
関数実行後:a = 3
これも、図で説明します。変数aは、まず、グローバル変数として定義(a = 3)されます。次に、グローバル変数aは、関数内の変数aに「参照渡し」で渡されます。このとき「参照渡し」ですので、同じアドレスi番地に保管したデータ3を共有します。次にa = 5の左辺に注目すると「変数 =」となっており、これは「変数の再定義」です。変数の再定義では関数内の変数aは、それまでの記憶を放置し、全く新しいアドレスk番地に、ローカル変数aを再定義します。この時点で、関数の外のグローバル変数aと関数内の変数aは、なんの関わりもない、それぞれ独立した変数になります。つまり、関数内のローカル変数aの変更が、関数の外のグローバル変数aに影響を与えることはありません。
余談となりますが、以下の参考記事も読んでみてください。グローバル変数を関数内へ受け渡す場合は、関数を定義した時点で、関数内の「変数の再定義」の有無が確認されていることがわかります。ですから、実際には下図中段の参照渡しする部分は省略され、いきなり、ローカル変数として「定義」されます。しかし、省略しているだけですので、「参照渡し」はすべて下図のような流れであると覚えておいた方が楽だと思います。ちなみに、引数による関数内へのデータの受け渡しの場合は下図中段は省略されません。
参考記事:
関数で変なエラーが出た:local variable 'x' referenced before assignment
関数への参照渡し後のリストの再定義
以下コード03は関数内でリストを変更していますが、コード01と違うのは、5行目をa = [5]としている点です。これは左辺が「変数名 =」となっていますので、要素の変更ではなく、「変数の再定義」です。つまり、ローカル変数として「変数の再定義」を行っているのです。再定義を行った時点で、外側のグローバル変数とは独立した別の変数になっています。したがって、関数内での変更が外側の関数に影響を与えません。
#コード03
a = [3]
def xxx():
a = [5] #変数の再定義
print('関数実行前:a =', a)
xxx()
print('関数実行後:a =', a)
#出力03
関数実行前:a = [3]
関数実行後:a = [3]
コード03は、関数内でリストを再定義しているので、流れはコード02の数値を再定義した場合と同じです。
変数の再定義では関数内の変数aは、それまでの記憶を放置し、全く新しいアドレスk番地に、ローカル変数aを再定義します。この時点で、関数の外のグローバル変数aと関数内の変数aは、それぞれ独立した変数になります。つまり、関数内のローカル変数aの変更が、関数の外のグローバル変数aに影響を与えることはありません。
なお、コード02の数値の再定義の場合と同様に、グローバル変数の関数への引き渡しの場合は、実際には下図中段の参照渡しは省略されます。