スコープは「変数や関数などが存在する場所」を示すと同時に、あるコード位置である名前を使ったときに「その名前を解決して対応する値(オブジェクト)を取り出すときにたどる道のりや視野」のようなものでもある。何度も述べているが「ローカルスコープ→グローバルスコープ→ビルトインスコープ」という順序で名前は検索され、より近い方にある名前が優先して使用される。そして、これを実現するために使われているのが、Pythonの「名前空間」だ。
「名前空間」とは「名前」と「その値」を関連付けるものだ(一般には既に何度か触れている「辞書」と呼ばれるデータ構造を使って実装されている)。名前空間はスコープに対応し、「ビルトイン名前空間」「グローバル名前空間」「関数のローカル名前空間」などがある。
Pythonのドキュメント「Python のスコープと名前空間」によれば、名前空間が作成されるタイミングについて次のようになる。
本稿の時点ではモジュールについてはきちんと説明をしていないので、理解が難しいかもしれないが、Jupyter Notebookなどのセル(に入力する変数定義など)は「__main__」という名前のモジュールに含まれることになっている。これはPythonが暗黙的に用意するグローバル変数「__name__」の値を調べると分かる。グローバル変数__name__の値は、現在実行中のモジュール名となるので、セルに以下を入力して、実行してみよう。
__name__
セルに「__name__」とだけ入力して実行すれば、その評価結果が得られる。実行結果を以下に示す。
すると、「__main__」と表示された。つまり、現在、セルにコードを入力して実行しているモジュールは「__main__」モジュールであり、セルに以下のコードを入力すれば、そこで定義している変数some_varはモジュールのローカルスコープ(とはモジュールのグローバルスコープ)で定義されるグローバル変数となる。
some_var = 'some_var'
あるスコープにどんな名前が存在しているかは、対応する名前空間を調べると分かる。関数のローカルスコープで定義された名前は、対応するローカル名前空間で管理されている。これを調べるのに使えるのが、Pythonに組み込みのlocals関数あるいはdir関数だ。
locals関数は現在のローカルスコープで定義されている名前とその値の組を要素とする辞書を返すものだ(辞書とは「キー: その値」という組を格納するためのデータ構造)。dir関数は引数なしで呼び出すと、現在のローカルスコープに存在する名前を要素とするリストを返す(リストは任意の個数の任意の種類のデータを格納するデータ構造)。
例えば、次のコードを考えてみよう(ローカル変数aの値を表示するprint関数呼び出しはコメントアウトした)。
def myfunc():
a = 'Python'
# print('a:', a)
print(locals())
myfunc()
print(locals())
このコードでは、myfunc関数のローカルスコープと、モジュールのトップレベル(グローバルスコープ)でlocals関数を呼び出して、その結果を画面に表示している。表示されるものにどんな違いがあるかを見てみよう(ここでは、グローバルスコープでのlocals関数の呼び出し結果がシンプルになるように、Pythonの実行環境をリセットしている)。
最初の「{'a': 'Python'}」という表示がmyfunc関数のローカルスコープで定義されている名前と、その値だ。非常にシンプルで、ローカル変数aとその値である文字列'Python'の組だけが格納されていることが分かる。
次の表示がモジュールのトップレベルで定義されている名前とその値が格納されている辞書の内容だ。直前に定義したグローバル変数some_var(その値は文字列'some var')や自分で定義したmyfunc関数なども見られるが、それ以外にも多くのものが格納されていることが分かる。これらはPythonのインタープリタや対話環境のセットアップ時に自動的に作成されるものだ。
コードの実行位置によって、ローカルスコープならびにローカル名前空間に存在するもの(名前)が異なることが分かったはずだ。myfunc関数の実行時に「a」という名前が使われたら、まずはローカル名前空間にある「a」(最初の「{'a': 'Python'}」にある「a」)が発見されることも想像できるはずだ。これがスコープの説明で話した名前解決で実際に行われていることだ。
もう一つのdir関数は、引数なしで呼び出すと、現在のローカルスコープ(関数内なら関数のローカルスコープ、モジュールのトップレベルならモジュールのローカルスコープ=グローバルスコープ)に存在する名前を含んだリストを戻す。興味のある方は、先ほどのコードでlocals関数を呼び出しているところを、dir関数呼び出しに変えて実行してみよう。
ローカルスコープに存在する名前を調べるには、今述べたlocals関数が使える。これと同様に、グローバルスコープに存在する名前を調べるには、globals関数が使える。セルにglobals関数呼び出しを書いて実行してみたところが以下だ。
本稿では「モジュールのトップレベルのローカルスコープとグローバルスコープは同じものになる」といった話をしていたが、これは「両者が同じ名前空間を参照する」という意味でもある。そこで、locals関数呼び出しの結果とglobals関数の呼び出しの結果を比べてみよう。といっても、上に示したようにその出力は非常に長いものになる。そこで次のコードを実行してみよう。
locals() == globals()
locals関数の戻り値とglobals関数の戻り値が等しいかを「==」演算子で比べれば、それらが同じかどうかが分かるはずだ。これを実行すると次のようになる。
「==」演算子による比較の結果はTrueなので、両者が同じものを参照していることが分かったはずだ。
これに対して、関数内でglobals関数を呼び出したときには、globals関数はその関数を含んでいるグローバルスコープにある名前(とその値)が格納された辞書を返すので、locals関数とは結果が異なるものになる。これも確認してみよう。
def myfunc():
a = 'Python'
print('locals:', locals())
print('globals:', globals())
myfunc()
これを実行すると、次のようになる。
myfunc関数内では、ローカル名前空間には先ほども見たようにローカル変数aだけがあり、グローバル名前空間にはより多くの名前が見られる(これらはモジュールのトップレベルと同じものだ)。
本稿では、Pythonの関数におけるローカル変数をスタート地点として、スコープや名前空間について見てきた。スコープと名前空間は密接な関係にあり、コードの特定箇所で名前を使用したときに、その名前を実際の値(オブジェクト)へと解決するには、スコープをたどりながら、各スコープの名前空間を検索していく。こうした動作自体は、すぐには覚えておく必要はないが、スコープには種類があり、最も内側のスコープ(ローカルスコープ)から最も外側のスコープ(ビルトインスコープ)へと検索が順次行われること、その過程で内側に存在する名前が使われることと、内側のスコープで定義したものは外側のスコープからは見えないことはよく覚えておこう。
次回は、オブジェクトとしての関数、ラムダ式など、関数について積み残している話題を取り上げる。
「Python入門」
Copyright© Digital Advantage Corp. All Rights Reserved.