関数のローカル変数と各種のスコープ、名前解決の順序、名前空間など、関数にまつわるスコープの話をまとめる。
* 本稿は2019年5月24日に公開された記事を、Python 3.12.0で動作確認したものです(確認日:2023年10月16日)。
前回は、Pythonの関数における引数の扱いに焦点を当て、位置引数やキーワード引数、可変長引数などを取り上げた。今回は、関数のローカル変数をスタート地点として、スコープや名前空間などについて見てみよう。
ここまで特に説明をしてこなかったが、変数にはそれを使える有効範囲がある。これを「スコープ」と呼ぶ。例として以下のような関数を定義して、変数のスコープについて考えてみよう。
def myfunc():
a = 'Python'
print('a:', a)
この関数の中では変数aを定義している。この変数は「myfunc関数の中でのみ利用できる」もので、「ローカル変数」と呼ぶ。「ローカル」(local)とは「局所的」という意味で、この場合は「関数を定義するブロック内」ということだ。なお、関数にパラメーターがあれば、それらも関数でローカル変数のように使える。
「myfunc関数の中でのみ利用できる」というのは、「その関数内で定義しているローカル変数を別の場所から利用することはできない」ことを意味する。つまり、次のようなコードは実行できない。
def myfunc():
a = 'Python'
print('a:', a)
myfunc() # myfunc関数を呼び出す(出力「a: Python」と表示される)
print(a) # myfunc関数で定義されているローカル変数aを利用
これを実行すると、次のようになる。
「NameError: name 'a' is not defined」(「a」という名前が定義されていない)というエラーメッセージが表示されている。これはmyfunc関数内で定義した変数aは「myfunc関数にローカル」であり、他の場所からは見えないからだ。これがローカル変数が持つ性質の一つの特徴である。
もう一つ。関数内でローカル変数などの値を操作しても、その変更の結果は関数内にとどまる。これにより、関数の外部にあるデータを意図せずに変更してしまう危険性が減少する(実際には、そうした操作は可能であり、それを意図して行うこともある)。これが「ローカル」「局所的」という言葉が意味するところだ。以下に例を示そう。
def myfunc():
a = 'Python'
print('a:', a)
a = 'Ruby'
myfunc()
print('a:', a)
このコードでは、myfunc関数のローカル変数と同名の変数aを関数の外部で定義してから、myfunc関数を呼び出している。なお、「関数の外部」(セルにインデントなしで入力するコード)のことを「モジュールレベル」「モジュールのトップレベル」などと呼び、モジュールレベルで定義する変数のことを「グローバル変数」(あるいは「モジュール変数」)と呼ぶことがある*1。グローバル変数はそのモジュールの全ての箇所からその値を参照できる。
*1 「グローバル」(global)とは「大域」といった意味で、プログラミングの世界では「プログラムのあらゆる箇所」を意味する。ただし、Pythonにおける「グローバル変数」(モジュール変数)は、「それを定義したモジュールのあらゆる箇所から利用できる」ことを意味し、複数のモジュールでプログラムが構成される場合、全てのモジュールに対して「グローバル」という意味ではない(あるモジュールで、別のモジュールのグローバル変数を「インポート」することで利用することは可能だ。これについてはモジュールを取り上げる際に説明する)。
話がそれたが、上のコードを実行するとどうなるだろう。
myfunc関数を呼び出すことで、そのローカル変数aには文字列'Python'が代入され、これを含んだ「a: Python」というメッセージが表示された。そして、関数の終了後には、モジュールレベルで定義したグローバル変数aの値を含んだ「a: Ruby」というメッセージが表示された。
グローバル変数aが'Ruby'という文字列値を持っている状態で、myfunc関数を呼び出したら、直後にグローバル変数aの値が'Python'に変わってしまっていたら、プログラマーはビックリするかもしれない。そのような事態を避けるために、Pythonでは基本的に「関数内での変数への代入はローカル変数の定義や、ローカル変数(やパラメーター)の値の変更」と解釈するようになっている(グローバル変数の値を関数内で明示的に行う方法もある。後述)。
ここで見たように、同じ「a」という名前の変数であっても、関数内で定義したローカル変数と、その外部で定義したグローバル変数は別モノであり、それが存在している場所やそれらを管理している場所が異なっているのだ。そして、これらを管理するために使うのが、「スコープ」や「名前空間」という機構である。
今までに見てきたように、コードの特定箇所で何らかの名前(変数名や関数名)を利用しようとしたときに、その名前がどんな値(オブジェクト)を参照しているかを決定するために使われる仕組みが「スコープ」といわれるものだ。また、この処理のことを「名前解決」と呼ぶ。ローカル変数が存在するスコープと、グローバル変数が存在するスコープは異なるもので、また現在プログラムのどこを実行しているかによって、名前解決で検索されるスコープも変化する。
例えば、関数定義は「ローカルスコープ」と呼ばれるスコープを形成する。つまり、myfunc関数で定義しているローカル変数aは、myfunc関数のローカルスコープに存在する。これに対して、モジュールレベルで定義するグローバル変数は「グローバルスコープ」(または「モジュールスコープ」)と呼ばれるスコープに存在する*2。グローバルスコープは、ローカルスコープを包含する形で存在している。さらに、グローバルスコープを包含する形で「ビルトインスコープ」と呼ばれるスコープもある。これは、組み込み(ビルトイン)の関数などにアクセスするために使われる。
関数定義の内部でさらに関数定義を行うと、スコープがより深くネストすることがある。これについては次回取り上げる。また、クラス定義では独自のスコープ規則があるが、これについてはクラスを説明する際に取り上げる。
*2 基本的には変数に代入するときには、それを行っているコード位置のローカルスコープが使われる。そして、モジュールのトップレベルにも「ローカルスコープ」は存在する。モジュールのトップレベルで変数を定義するときには、「モジュールのローカルスコープ」が使われるということだ。ただし、モジュールのローカルスコープは、モジュールのグローバルスコープと同じものになるので、関数のローカルスコープとの差がハッキリするように、ここでは「グローバルスコープに存在する」と表現している。詳細については「Python のスコープと名前空間」を参照されたい。
他の言語の経験者に向けて注意を述べておくと、Pythonではスコープの形成が他の言語よりもシンプルになっている。Python以外の言語では、if文やwhile文のブロックや波かっこ「{}」で囲まれた範囲が新しくスコープを作る場合もあるが、Pythonではそのようなことはない。モジュールより下のレベルでスコープを新たに作り出すのは基本的には関数定義だけだ。
Copyright© Digital Advantage Corp. All Rights Reserved.