Help us understand the problem. What is going on with this article?

打倒、Pythonのグローバル宣言

global宣言

テーマはPython3のglabal宣言のスコープについて。
定義の部分が曖昧な理解だったため数時間無駄にしてしまったが、いろいろ調べて勉強になったので記録に残しておきます。

失敗した例

すごく簡単に書くとこんなコード。あくまでサンプルです。

sample.py
def func():
    global x
    x = 20
    print(f'funcのx={x}')

def main():
    x = 10
    func()
    print(f'mainのx={x}')

if __name__ == '__main__':
    main()

どちらも20が出力されるかと思いきや。

bash
$ python3 sample1.py 
funcのx=20
mainのx=10

なんで??!!global宣言しとるやんけ!!とか思い数時間あれこれ調べたが解決。↓↓の通り。

大事なのはスコープ

global xでの宣言は下記2つを意味する。

  • グローバルスコープにオブジェクトxがあれば、そのコードブロック内でグローバル変数xとして扱える(=同一identityを参照)
  • グローバルスコープにオブジェクトxがなければ、新しくグローバル変数xとして宣言(グローバルスコープからもこのxは扱える

先ほどのコードだと実はxはグローバルスコープではなく、main()(=ローカルスコープ)内に存在していた。つまりfunc()内で宣言したグローバル変数xはmain()内のローカル変数xとは全く別物だった。

解決策

main()というローカルスコープ内ではなく、グローバルスコープ内でxを宣言しておきます。

sample.py
def func():
    global x
    x = 20
    print(f'funcのx={x}')

def main():
    # x = 10 ここはローカルスコープ
    func()
    print(f'mainのx={x}') #グローバル変数の参照はできる(詳細は本記事下部)

if __name__ == '__main__':
    x = 10 #グローバルスコープでxを定義
    main()
bash
$ python3 test.py 
funcのx=20
mainのx=20

def main & if __name__ == ‘__main__’:なんてやり方をせずに、単純にmain()部分の命令を書き込んでおけば、このトラブルは起こらなかっただろう・・・。

global宣言はそのコードブロック内だけで有効

sample.py
def func():
    #global x ここではglobal宣言しない場合
    x = 20
    print(f'funcのx={x}')

def main():
    global x
    func()
    print(f'mainのx={x}')

if __name__ == '__main__':
    x = 10 #グローバルスコープでxを定義
    main()
bash
$ python3 sample1.py
funcのx=20
mainのx=10

main()内でglobal宣言しているので、そこから呼び出されるfunc()内でglobal宣言は要らないかと思いきや、そんなことはなかった。

理由を考えるにあたり、ドグマである公式ドキュメントを参照。改めて読むと結構わかりやすいのね。

global 文は、現在のコードブロック全体で維持される宣言文です。 global 文は、列挙した識別子をグローバル変数として解釈するよう指定することを意味します。 global を使わずにグローバル変数に代入を行うことは不可能ですが、自由変数を使えばその変数をグローバルであると宣言せずにグローバル変数を参照することができます。

「現在のコードブロック全体」で宣言は維持されるということは、逆に言うとコードブロック外には効果は及ばないということか。
func()内でx = 20が代入(定義)された瞬間、グローバル変数が書き換わるのではなく、ローカル変数xが定義されたのだと考えられる。

解決策

sample.py
def func():
    global x #func()内でもグローバル宣言
    x = 20
    print(f'funcのx={x}')

def main():
    global x
    func()
    print(f'mainのx={x}') 

if __name__ == '__main__':
    x = 10 #グローバルスコープでxを定義
    main()
bash
$ python3 test.py 
funcのx=20
mainのx=20

main()内でもfunc()内でもglobal宣言しておけば、どちらも同一にグローバルスコープのxをグローバル変数xとして扱えるようになる。

global宣言がなくても参照はできる。

参考まで。
global宣言しなくてもグローバルスコープのオブジェクトならば参照はできる。書き換えはもちろんできない。値を代入した瞬間、ローカル変数として定義される

sample.py
def func():
    x = 20
    print(f'funcのx={x}')

def main():
    func()
    print(f'mainのx={x}') 

if __name__ == '__main__':
    x = 10 #グローバルスコープでxを定義
    main()
bash
$ python3 test.py 
funcのx=20
mainのx=10

どちらもglobal宣言していない。global変数の参照だけならできるのでmain()はグローバルスコープのxを参照できる。func()は中でx = 20を実行した瞬間にローカル変数xが定義され、グローバルスコープのグローバル変数xとは全く別物として扱われるようになる。

まとめ

慣れないことをやると色んなところで詰まるね。解決のためにドキュメント読んだりする中で学びが深まる(と信じたい)のでよしとしましょう。

以下、今回勉強する中で分かりやすかった記事など(本文中にもリンク張ってますが)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした