Pythonにはglobal/nonlocalという他の言語ではあまり見られないキーワードがある。これらは現在のスコープからグローバルスコープあるいは自分を囲むスコープに存在する変数への再代入を可能にする。
先ほどの「関数内でグローバル変数と同じ名前の変数に代入をしてみる」例では、グローバル変数の値が変わるのではなく、ローカルスコープに新たに名前が導入されたが、globalキーワードを付けて変数を宣言することで(global文)、その名前はグローバルスコープに存在するものとして取り扱われる。
globalキーワードの使用例を以下に示す。
name = "insider.net"
def test():
global name
name = "windows server insider"
print(locals())
test()
print(name)
globalキーワードには続けて、グローバルスコープに存在するとして扱う名前を記述する。「global name = ……」のようにグローバル変数の宣言と代入(再束縛)を同時にはできないので注意しよう。
実行結果は次のようになる。
>>> name = "insider.net"
>>> def test():
... global name
... name = "windows server insider"
... print(locals())
...
>>> test()
{}
>>> print(name)
windows server insider
関数内部での組み込み関数localsの呼び出しを見ると分かる通り、ローカルスコープにはnameという名前は存在していない。その一方で「name = "windows server insider"」行によりグローバル変数nameの値が変化している(「windows server insider」に再束縛された)ことが分かる。
なお、上の例では最初にグローバル変数nameが確実に存在しているように「name = "insider.net"」行を実行しているが、globalキーワードに続けて記述する名前は宣言時にグローバルスコープに存在している必要はない。よって、上の状態から以下のコードを実行してもエラーは発生しない(実行結果は割愛)。
del name
test()
print(name)
もう1つのキーワードnonlocalはローカルスコープを囲むスコープに存在する名前に対して、ローカルスコープから代入を行えるようにするものだ。以下に例としてカウンターを作成する関数を示す。最初にエラーとなる例を示そう。
def makecounter():
n = 0
def count():
n += 1
return n
return count
counter = makecounter()
print(counter())
これは典型的なクロージャであり、外側のスコープで定義されている変数nの値を、内側の関数で参照、変更している。実際にこれを利用しようとすると次のようになる。
>>> def makecounter():
... n = 0
... def count():
... n += 1
... return n
... return count
...
>>> counter = makecounter()
>>> print(counter())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in count
UnboundLocalError: local variable 'n' referenced before assignment
内側の関数ではローカルスコープの外部にある名前を参照はできるが、その値を変更することはできない(上のglobalキーワードの例では、代入しようとすると同じ名前が新たにローカルスコープに導入されていた。今回は導入される前に、その値を変更しようとしている)。そのため、「n += 1」行は「未定義の変数の値を変更しようとした」と解釈されて、実行時に「UnboundLocalError」例外が発生している。こうした状況を避けるためにnonlocalキーワードが利用できる。上のコードを修正すると次のようになる。
def makecounter():
n = 0
def count():
nonlocal n
n += 1
return n
return count
counter = makecounter()
print(counter())
print(counter())
print(counter())
「nonlocal n」行で名前nがローカルスコープを囲むスコープに存在していることを明示することで、その値を変更できるようになった。実際の実行結果を以下に示す(確かに動作しているかを確認するために「print(counter())」行の数も増やしている)。
>>> def makecounter():
... n = 0
... def count():
... nonlocal n
... n += 1
... return n
... return count
...
>>> counter = makecounter()
>>> print(counter())
1
>>> print(counter())
2
>>> print(counter())
3
このようにクロージャから外側のスコープで定義されている変数の値を変更する必要があるといった場合にはnonlocalは便利に使えるはずだ。また、ここでは組み込み関数localsの呼び出しは行っていないが、前ページの最後の例と同様な結果となる(つまり、外側のスコープで定義されている変数が「自由変数」としてローカルな名前空間に存在するものとして扱われていることが分かる)。
今回はPythonにおけるスコープと名前空間に関連する事項を取り上げた。次回はPythonの例外を取り上げる。なお、PTVSの[Interactive]ウィンドウは本稿で見たように変数__builtins__が参照するオブジェクトが通常の対話環境と異なるが、その一方で対話環境のスコープを__main__モジュール以外のものに変更できる。興味のある方はPTVSのリポジトリ内にあるドキュメント「Interactive REPL」(英語)などを参照されたい。
Copyright© Digital Advantage Corp. All Rights Reserved.