驚いたところ
以下を実行するとどうなるでしょうか?
>>> HOGE = 'aaa'
>>>
>>> def func(is_b):
>>> if is_b:
>>> global HOGE
>>> HOGE = 'bbb'
>>> else:
>>> HOGE = 'ccc'
>>>
>>> print(HOGE)
>>> func(True)
>>> print(HOGE)
>>> func(False)
>>> print(HOGE)
python 3.8.8で実行すると以下のようになりました
aaa
bbb
ccc
何故else側まで反映されてるの?
ということでちょこちょこ触って挙動を確認したのをまとめてみます
1. 関数内で参照は出来るけど上書きはされない
pythonではグローバル変数を関数内から参照できるが上書きされないという扱いになるのはみなさんご存知だと思います
>>> HOGE = 'aaa'
>>>
>>> def func():
>>> print(HOGE)
>>>
>>> func()
aaa
>>> HOGE = 'aaa'
>>>
>>> def func():
>>> HOGE = 'bbb'
>>> print(HOGE)
>>>
>>> print(HOGE)
>>> func()
>>> print(HOGE)
aaa
bbb
aaa
オブジェクトのIDを確認すれば何が起こっているか分かります
>>> HOGE = 'aaa'
>>>
>>> def func():
>>> print('2:', HOGE, id(HOGE))
>>> HOGE = 'bbb'
>>> print('3:', HOGE, id(HOGE))
>>>
>>> print('1:', HOGE, id(HOGE))
>>> func()
>>> print('4:', HOGE, id(HOGE))
1: aaa 2421120646128
2: aaa 2421120646128
3: bbb 2421061141232
4: aaa 2421120646128
func()内で関数内スコープの変数HOGEが新たに定義されていて、元のグローバル変数のHOGEはそのまま残っているというわけです
2. 上書きしたいときはglobalを宣言する
上書きしたいときはglobal HOGEのように宣言することで変更がグローバル変数側に上書きされるようになります
>>> HOGE = 'aaa'
>>>
>>> def func():
>>> global HOGE
>>> print('2:', HOGE, id(HOGE))
>>> HOGE = 'bbb'
>>> print('3:', HOGE, id(HOGE))
>>>
>>> print('1:', HOGE, id(HOGE))
>>> func()
>>> print('4:', HOGE, id(HOGE))
1: aaa 2421121446064
2: aaa 2421121446064
3: bbb 2421061141232
4: bbb 2421061141232
新しいIDのオブジェクトをつくっているのは同じですが、グローバル変数のHOGEのIDが関数内でつくったものになっています
3. 後からglobalしたらどうなるか
下のように、後からglobalしようとすると既に関数内スコープの変数HOGEが既にある状態で、global HOGEするのはダメですよとSyntax errorになります
>>> HOGE = 'aaa'
>>>
>>> def func():
>>> print('2:', HOGE, id(HOGE))
>>> HOGE = 'bbb'
>>> print('3:', HOGE, id(HOGE))
>>> global HOGE
>>>
>>> print('1:', HOGE, id(HOGE))
>>> func()
>>> print('4:', HOGE, id(HOGE))
SyntaxError: name 'HOGE' is used prior to global declaration
4. globalは関数ごとに宣言しないといけない
globalは宣言した関数でのみ有効で他の関数には影響しませんので、必要なところだけ宣言するという書き方ができます
>>> HOGE = 'aaa'
>>>
>>> def func1():
>>> HOGE = 'bbb'
>>>
>>> def func2():
>>> global HOGE
>>> HOGE = 'ccc'
>>>
>>> def func3():
>>> HOGE = 'ddd'
>>>
>>> print(HOGE)
>>> func1()
>>> print(HOGE)
>>> func2()
>>> print(HOGE)
>>> func3()
>>> print(HOGE)
aaa
aaa
ccc
ccc
5. if文分岐内でglobal宣言するとどうなるのか
そろそろ本題です。
以下を実行するとどうなるでしょうか?
>>> def func(is_b):
>>> if is_b:
>>> global HOGE
>>> HOGE = 'bbb'
>>> else:
>>> global HOGE
>>> HOGE = 'ccc'
SyntaxError: name 'HOGE' is assigned to before global declaration
なんとSyntax errorになってしまいます。それぞれの分岐においてはじめて宣言するglobalであるのに、既に宣言されているからダメですよと言われてもなんの事なのか分かりません。とりあえず後の方のglobalを削ってみたところ、冒頭のように更に混乱する結果になります。
>>> HOGE = 'aaa'
>>>
>>> def func(is_b):
>>> if is_b:
>>> global HOGE
>>> HOGE = 'bbb'
>>> else:
>>> HOGE = 'ccc'
>>>
>>> print(HOGE)
>>> func(True)
>>> print(HOGE)
>>> func(False)
>>> print(HOGE)
aaa
bbb
ccc
else側においてもグローバル変数のHOGEが上書きされていて、global HOGEした動作になっています。
つまりglobalは1行ずつ実行される命令文ではなくて、書かれた行以降に書かれている指定の変数が出てきたときにどう扱うのかpythonに指示する一文なわけです。なので、例えば以下のように絶対に実行されない行に書いてもglobal HOGEは有効になるわけです。
>>> HOGE = 'aaa'
>>>
>>> def func():
>>> if False:
>>> global HOGE
>>> HOGE = 'bbb'
>>> else:
>>> HOGE = 'ccc'
>>>
>>> print(HOGE)
>>> func()
>>> print(HOGE)
aaa
ccc
じゃあ後で書く側だけglobal HOGEするのが出来るのかというと出来ません
>>> HOGE = 'aaa'
>>>
>>> def func(is_b):
>>> if is_b:
>>> HOGE = 'bbb'
>>> else:
>>> global HOGE
>>> HOGE = 'ccc'
>>>
>>> print(HOGE)
>>> func(True)
>>> print(HOGE)
>>> func(False)
>>> print(HOGE)
SyntaxError: name 'HOGE' is assigned to before global declaration
グローバル変数の値だけ扱うのか参照して扱うのかは関数単位で決めておかないといけないという事ですね。
まとめ
ということで、global declarationの挙動にちょっと驚いたというお話でした。
関数内からグローバル変数を書き換えるという恐ろしい書き方は出来るだけ避けたいところですが、どうしてもやらないといけないときの為に自分で動作を確認しておいた方が良いかもしれません。
レッツトライ