#global宣言
テーマはPython3のglabal宣言のスコープについて。
定義の部分が曖昧な理解だったため数時間無駄にしてしまったが、いろいろ調べて勉強になったので記録に残しておきます。
#失敗した例
すごく簡単に書くとこんなコード。あくまでサンプルです。
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が出力されるかと思いきや。
$ 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を宣言しておきます。
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()
$ python3 test.py
funcのx=20
mainのx=20
def main
& if __name__ == ‘__main__’:
なんてやり方をせずに、単純にmain()部分の命令を書き込んでおけば、このトラブルは起こらなかっただろう・・・。
#global宣言はそのコードブロック内だけで有効
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()
$ python3 sample1.py
funcのx=20
mainのx=10
main()内でglobal宣言しているので、そこから呼び出されるfunc()内でglobal宣言は要らないかと思いきや、そんなことはなかった。
理由を考えるにあたり、ドグマである公式ドキュメントを参照。改めて読むと結構わかりやすいのね。
global 文は、現在のコードブロック全体で維持される宣言文です。 global 文は、列挙した識別子をグローバル変数として解釈するよう指定することを意味します。 global を使わずにグローバル変数に代入を行うことは不可能ですが、自由変数を使えばその変数をグローバルであると宣言せずにグローバル変数を参照することができます。
**「現在のコードブロック全体」**で宣言は維持されるということは、逆に言うとコードブロック外には効果は及ばないということか。
func()内でx = 20
が代入(定義)された瞬間、グローバル変数が書き換わるのではなく、ローカル変数xが定義されたのだと考えられる。
###解決策
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()
$ python3 test.py
funcのx=20
mainのx=20
main()内でもfunc()内でもglobal宣言しておけば、どちらも同一にグローバルスコープのxをグローバル変数xとして扱えるようになる。
#global宣言がなくても参照はできる。
参考まで。
global宣言しなくてもグローバルスコープのオブジェクトならば参照はできる。書き換えはもちろんできない。値を代入した瞬間、ローカル変数として定義される。
def func():
x = 20
print(f'funcのx={x}')
def main():
func()
print(f'mainのx={x}')
if __name__ == '__main__':
x = 10 #グローバルスコープでxを定義
main()
$ python3 test.py
funcのx=20
mainのx=10
どちらもglobal宣言していない。global変数の参照だけならできるのでmain()はグローバルスコープのxを参照できる。func()は中でx = 20
を実行した瞬間にローカル変数xが定義され、グローバルスコープのグローバル変数xとは全く別物として扱われるようになる。
#まとめ
慣れないことをやると色んなところで詰まるね。解決のためにドキュメント読んだりする中で学びが深まる(と信じたい)のでよしとしましょう。
以下、今回勉強する中で分かりやすかった記事など(本文中にもリンク張ってますが)
- globalとnonlocalの宣言に関係する疑問点を検証してみました | Snow Tree in June
- Python global宣言【書き換えの時に宣言が必要な理由】
- nonlocal一題 - Qiita ←こちらは本記事と直接関係ないですが、わかりやすかったのでご紹介。