Pythonにおける名前空間とスコープを理解する上でのポイントを押さえよう特集:Visual Studioで始めるPythonプログラミング(3/4 ページ)

» 2016年12月09日 05時00分 公開
[かわさきしんじInsider.NET編集部]

また試してみよう

 では、話を戻して、前ページで示した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は存在しない

 見ての通り、ローカルスコープの名前空間には名前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


ネストした関数のローカルな名前空間には外側のスコープに存在する変数xとyも存在している

 関数内からグローバル変数にアクセスしても、ローカルな名前空間にはそれが存在していなかったのとは異なり、今回はネストした関数adderでは外側のスコープの変数xとyも名前空間内に存在している。このようにクロージャを形成するネストした関数では、それを囲むスコープ内の変数も名前空間に導入される(それらの変数を使用している場合)。このような変数のことを「自由変数」と呼ぶ(「Python言語リファレンス」の「4.2.1. 名前の束縛」では「ある変数があるコードブロック内で使われていて、そのブロックで定義はされていないなら、それは自由変数 (free variable)」となっている。この範疇にはグローバルな名前空間に存在する変数は含まれず、外側のスコープの名前空間に存在する変数が「自由変数」となるようだ)。

 先ほど「グローバル変数やローカルスコープを囲むスコープの変数を操作する方法については後述する」と述べた。本稿の終わりとして、次ページではそのために用意されているglobal/nonlocalキーワードを紹介する(globalキーワードは「global文」で使用されるキーワードであり、組み込み関数の「globals」とは異なることに注意)。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。