今さらPython3 (31) - ネームスペースとスコープ

第4章も終盤に差し掛かってきました。今日は、あの謎が解けるかも。

入門 Python 3

入門 Python 3

変数のスコープ

>>> animal = 'fruitbat'
>>> def print_global():
...     print('inside print_global:', animal)
...
>>> print('at the top level:', animal)
at the top level: fruitbat
>>> print_global()
inside print_global: fruitbat
>>>

globalネームスペースで定義した変数はglobal変数となる。上の例だと、defの外側のglobal領域にanimalという変数を定義して、その変数が関数print_global()の中でも参照されている。実行すると、関数の中で呼んだヤツも'fruitbat'を返していると。

>>> def change_and_print_global():
...     print('inside change_and_print_global:', animal)
...     animal = 'wombat'
...     print('after the change:', animal)
...
>>> change_and_print_global()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in change_and_print_global
UnboundLocalError: local variable 'animal' referenced before assignment
>>>

今度の例は、change_and_print_global()の1行目でglobal変数としてのanimalをprint()しようとして、その後値を変更、さらに変更後の値をprint()しようとしたけど、エラーが出て怒られたパターン。メッセージを見ると、ローカル変数animalを定義(割当)する前に使われているよと言っている。

>>> def change_local():
...    animal = 'wombat'
...    print('inside change local:', animal)
...
>>> animal
'fruitbat'
>>> change_local()
inside change local: wombat
>>>

change_local()の中では、animalはグローバル変数ではなくて、ローカル変数として認識されたので、今回はエラーとならなかったと理解すれば良さそう。グローバル変数を照会していなくて、いきなりローカル変数として扱いだしたのでお咎めなし。なるほど。

少し本の例と違ったので、本の通りにやり直し。

>>> def change_local():
...     animal = 'wombat'
...     print('inside change_and_print_global:', animal, id(animal))
...
>>> change_local()
inside change_and_print_global: wombat 36752224
>>> animal, id(animal)
('fruitbat', 43126280)

id()を使って、同じものか別物かをハッキリさせようという魂胆ですね。分かります。

>>> animal = 'fruitbat'
>>> def change_and_print_global():
...     global animal
...     animal = 'wombat'
...     print('inside change_and_print_global:', animal)
...
>>> animal
'fruitbat'
>>> change_and_print_global()
inside change_and_print_global: wombat
>>> animal
'wombat'
>>>

animalはグローバル変数だぜぇ。と宣言した後に値を書き換えているので、実行後に改めてanimalの値を呼び出すと上書きされた値になったと。id()使って確認してみる?

>>> animal = 'fruitbat'
>>> animal, id(animal)
('fruitbat', 43126280)
>>> def change_and_print_global():
...     global animal
...     animal = 'wombat'
...     print('inside of function:', animal, id(animal))
...
>>> change_and_print_global()
inside of function: wombat 36752224
>>> animal
'wombat'
>>> id(animal)
36752224

ほう。関数の中でグローバル変数animalのidは書き換わるんだね。それでも、print()したときと処理後に出した値は同じなので、予想通りの結果と言って良いはず。

>>> locals()
{'animal': 'wombat', '__spec__': None, '__name__': '__main__', 'change_and_prin
_global': <function change_and_print_global at 0x02922030>, '__builtins__': <mo
ule 'builtins' (built-in)>, 'square_it': <function square_it at 0x02922108>, 'c
oler_add_ints': <function document_it.<locals>.new_function at 0x022F3DB0>, '__
oader__': <class '_frozen_importlib.BuiltinImporter'>, 'document_it': <function
document_it at 0x022F3CD8>, 'change_local': <function change_local at 0x022F3F1
>, 'print_global': <function print_global at 0x022F3FA8>, 'add_ints': <function
square_it.<locals>.new_function at 0x02922150>, '__package__': None, '__doc__':
None, 'add_ins': <function add_ins at 0x022F3E88>}
>>> globals()
{'animal': 'wombat', '__spec__': None, '__name__': '__main__', 'change_and_prin
_global': <function change_and_print_global at 0x02922030>, '__builtins__': <mo
ule 'builtins' (built-in)>, 'square_it': <function square_it at 0x02922108>, 'c
oler_add_ints': <function document_it.<locals>.new_function at 0x022F3DB0>, '__
oader__': <class '_frozen_importlib.BuiltinImporter'>, 'document_it': <function
document_it at 0x022F3CD8>, 'change_local': <function change_local at 0x022F3F1
>, 'print_global': <function print_global at 0x022F3FA8>, 'add_ints': <function
square_it.<locals>.new_function at 0x02922150>, '__package__': None, '__doc__':
None, 'add_ins': <function add_ins at 0x022F3E88>}
>>>

local(), global()で各ネームスペースに保存されている情報が辞書(dict)形式で出力する事が出来るんですね。でも、両方とも同じじゃね?と思ったけど、よく考えたら今いる場所がグローバル領域なので、そこでlocal()すればグローバルそのものがローカルになるという事だと理解。

>>> animal = 'fruitbat'
>>> def change_local():
...     animal = 'wombat'    #local variable
...     print('locals:', locals())
...
>>> animal
'fruitbat'
>>> change_local()
locals: {'animal': 'wombat'}
>>>

・・・local()はこうやって使わないとね。

>>> print('globals:', globals()) #reformatted a little for presentation
globals: {'animal': 'fruitbat', '__spec__': None, '__name__': '__main__', 'chang
e_and_print_global': <function change_and_print_global at 0x02922030>, '__builti
ns__': <module 'builtins' (built-in)>, 'square_it': <function square_it at 0x029
22108>, 'cooler_add_ints': <function document_it.<locals>.new_function at 0x022F
3DB0>, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'document_it':
 <function document_it at 0x022F3CD8>, 'change_local': <function change_local at
 0x022F3F60>, 'print_global': <function print_global at 0x022F3FA8>, 'add_ints':
 <function square_it.<locals>.new_function at 0x02922150>, '__package__': None,
'__doc__': None, 'add_ins': <function add_ins at 0x022F3E88>}
>>> animal
'fruitbat'
>>>

これは、さっき出力してしまったのと同じはず。

_ と __ の疑問を解く


名称の最初と最後が2つのアンダースコアで囲まれたものは、Pythonにあらかじめ予約されています。なぜなら、普通のアプリケーションの開発者が使いそうもないから。

>>> def amazing():
...     ''' This is the amazing function.
...     Want to see it again?'''
...     print('This function is named:', amazing.__name__)
...     print('And its docstring is:', amazing.__doc__)
...
>>> amazing()
This function is named: amazing
And its docstring is:  This is the amazing function.
    Want to see it again?
>>>

これ以外には__main__とかもあるよねって、これは知ってるっつーの。アンスコ1個だけのパターンについての解説が欲しかったのに。。。アンスコ1個の時は外から参照しないとか暗黙のルールがあった気がする。

きっとClassのところで解説があるはず!(まだ見てないけど)

(つづく)