23
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

pythonのglobalの動作にちょっと驚いた

Last updated at Posted at 2021-07-10

驚いたところ

以下を実行するとどうなるでしょうか?

>>> 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の挙動にちょっと驚いたというお話でした。

関数内からグローバル変数を書き換えるという恐ろしい書き方は出来るだけ避けたいところですが、どうしてもやらないといけないときの為に自分で動作を確認しておいた方が良いかもしれません。

レッツトライ

23
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?