では、話を戻して、前ページで示した3行のコードの実行結果を(コマンドプロンプトから起動した対話環境で)見てみよう。その結果は次のようになっていた(改行は筆者が適宜挿入している。以下同様)。
>>> locals()
{'__package__': None, '__name__': '__main__',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__doc__': None, '__builtins__': <module 'builtins' (built-in)>}
>>> globals()
{'__package__': None, '__name__': '__main__',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__doc__': None, '__builtins__': <module 'builtins' (built-in)>}
>>> locals() == globals()
True
最後の「locals() == globals()」の結果が「True」となっていることから分かるように、対話環境のローカルスコープの名前空間の内容とグローバルスコープの名前空間の内容は最初は同一だ。先にも述べたが、対話環境は__main__モジュールに含まれる形で実行されていて、そこで何らかの名前を定義した場合、それは__main__モジュールでグローバルかつ__main__モジュールにローカルな名前となる。試しに変数を1つ定義してみよう。
name = "insider.net"
locals()
globals()
これを対話環境で実行すると、その結果は次のようになる。
>>> name = "insider.net"
>>> locals()
{'__package__': None, '__name__': '__main__', 'name': 'insider.net',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None, '__doc__': None,
'__builtins__': <module 'builtins' (built-in)>}
>>> globals()
{'__package__': None, '__name__': '__main__', 'name': 'insider.net',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None, '__doc__': None,
'__builtins__': <module 'builtins' (built-in)>}
今度は関数を定義してみよう。
def foo():
bar = "bar"
print("local namespace in foo:", locals())
print("global namespace in foo:", globals())
foo()
print("local namespace in toplevel:", locals())
print("global namespace in toplevel:", globals())
実行結果は次のようになる。
>>> def foo():
... bar = "bar"
... print("local namespace in foo:", locals())
... print("global namespace in foo:", globals())
...
>>> foo()
local namespace in foo: {'bar': 'bar'}
global namespace in foo: {'__package__': None, '__name__': '__main__',
'name': 'insider.net',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__doc__': None, 'foo': <function foo at 0x0000015133E57F28>,
'__builtins__': <module 'builtins' (built-in)>}
>>> print("local namespace in toplevel:", locals())
local namespace in toplevel: {'__package__': None, '__name__': '__main__',
'name': 'insider.net',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__doc__': None, 'foo': <function foo at 0x0000015133E57F28>,
'__builtins__': <module 'builtins' (built-in)>}
>>> print("global namespace in toplevel:", globals())
global namespace in toplevel: {'__package__': None, '__name__': '__main__',
'name': 'insider.net',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__doc__': None, 'foo': <function foo at 0x0000015133E57F28>,
'__builtins__': <module 'builtins' (built-in)>}
定義された関数はモジュールのグローバルスコープ/ローカルスコープの名前空間に登録されていることが分かる(強調書体で示した「'foo': <function foo at 0x0000015133E57F28>」という出力結果)。一方、関数内でローカルな変数は関数内部のローカルな名前空間にのみ存在していることも分かる(関数foo呼び出しの出力結果の先頭行)。
今度は先ほどトップレベルで定義した変数nameを関数foo内で使ってみよう。なお以下では、関数内部での組み込み関数globalsおよびモジュールトップレベルでの組み込み関数locals/globalsの呼び出しは省略する(ので、PTVSの[Interactive]ウィンドウでも表示結果は恐らく変わらないはずだ)。
def foo():
bar = 'bar'
print(name, bar)
print(locals())
foo()
実行結果は次のようになる。
>>> def foo():
... bar = 'bar'
... print(name, bar) # 変数nameとbarの内容を半角スペースで区切って表示
... print(locals())
...
>>> foo()
insider.net bar
{'bar': 'bar'}
見ての通り、ローカルスコープの名前空間には名前nameは存在しないが、きちんと使用できている。これが先に述べたローカルスコープから始まる名前の可視化ということだ。名前がローカルスコープにないので、グローバルスコープに存在する名前が検索されて参照されている。
では、関数内でグローバルスコープにある変数(グローバル変数)と同名の変数に値を代入してみるとどうなるだろう。
def foo():
name = "windows server insider"
bar = "bar"
print(locals())
foo()
print(name)
実行結果を以下に示す。
>>> def foo():
... name = "windows server insider"
... bar = "bar"
... print(locals())
...
>>> foo()
{'name': 'windows server insider', 'bar': 'bar'}
>>> print(name)
insider.net
この場合には、グローバルスコープに存在する変数nameの値が書き換えられる(再束縛される)のではなく、ローカルスコープに新たに名前が導入される(なお、グローバル変数や「ローカルスコープを囲むスコープ」の変数を操作する方法については後述する)。
このページの最後の例として「ローカルスコープを囲むスコープ」を作ってみよう。
def makeadder100and(x):
y = 100
print(locals())
def adder(z): # ネストした関数
print(locals())
return x + y + z # ローカルスコープを囲むスコープ内の変数にアクセス
return adder
add101 = makeadder100and(1)
add101(100)
関数makeadder100andは、パラメーターxに値を受け取るとともに、その内部でローカル変数yと関数adderを定義している(戻り値は関数adder)。ネストした関数adderではパラメーターzに受け取った値と、それを「囲むスコープ」に存在する変数xとyにもアクセスをしている。そして、それぞれの関数の内部ではローカルな名前空間に存在する値を出力している。
実行結果は次のようになる。
>>> def makeadder100and(x):
... y = 100
... print(locals())
... def adder(z):
... print(locals())
... return x + y + z
... return adder
...
>>> add101 = makeadder100and(1)
{'y': 100, 'x': 1} # 関数makeadder100andの名前空間の内容
>>> add101(100)
{'z': 100, 'x': 1, 'y': 100} # 関数adderの名前空間の内容
201
関数内からグローバル変数にアクセスしても、ローカルな名前空間にはそれが存在していなかったのとは異なり、今回はネストした関数adderでは外側のスコープの変数xとyも名前空間内に存在している。このようにクロージャを形成するネストした関数では、それを囲むスコープ内の変数も名前空間に導入される(それらの変数を使用している場合)。このような変数のことを「自由変数」と呼ぶ(「Python言語リファレンス」の「4.2.1. 名前の束縛」では「ある変数があるコードブロック内で使われていて、そのブロックで定義はされていないなら、それは自由変数 (free variable)」となっている。この範疇にはグローバルな名前空間に存在する変数は含まれず、外側のスコープの名前空間に存在する変数が「自由変数」となるようだ)。
先ほど「グローバル変数やローカルスコープを囲むスコープの変数を操作する方法については後述する」と述べた。本稿の終わりとして、次ページではそのために用意されているglobal/nonlocalキーワードを紹介する(globalキーワードは「global文」で使用されるキーワードであり、組み込み関数の「globals」とは異なることに注意)。
Copyright© Digital Advantage Corp. All Rights Reserved.