抜粋翻訳 PEP3104: Access to Names in Outer Scopes

概要

ネストしたスコープをサポートする多くの言語では、コードは最も近いスコープの名前を参照したり再束縛(代入)したりできる。現在のところPythonは外側のスコープの任意の名前を参照できるが再束縛は2つのスコープの中でしかできない。ローカルスコープ(単なる代入を使う)と、モジュールグローバルのスコープ( ``global`` 宣言を使う)だ。
この制限はなんどもPython-Devメーリングリストやその他で話題にあがり、多くの議論とこの制限を取り除く方法の提案につながった。このPEPでは提案された数多くの選択肢を総括し、それぞれに指摘された長所と他所を付記する。

理由

バージョン2.1以前、Pythonのスコープの扱いは標準的なCに似たものであった。ファイルの中には2つのスコープレベルしか存在しない。グローバルとローカルである。Cでは、これは関数の定義がネストできないことによる自然な結果である。しかしPythonでは、関数は通常トップレベルで定義されるとはいえ、関数定義はどこでも実行できる。これは構文上はPythonにネストしたスコープがあるように見せる。しかしその機能はない。この不一致はプログラマを驚かせる。

たとえばトップレベルで動いていた再帰関数は他の関数の中に入れると動かなくなるだろう。なぜなら再帰関数自体の名前が再帰関数の本体のスコープからは見えなくなるからだ。これは関数はどこに置かれているかにかかわらず同じように振舞うべきだ、という直感に反する。下に例を挙げる::

    def enclosing_function():
        def factorial(n):
            if n < 2:
                return 1
            return n * factorial(n - 1)  # fails with NameError
        print factorial(5)

Python 2.1では外側のスコープすべてで束縛された名前が見えるようになり、より静的にネストしたスコープに近づいた (PEP 227を参照)この変更によって上で挙げたコード例は期待通りに動くようになった。しかし、全ての名前への代入は暗黙的にその名前をローカルとして宣言するので外側のスコープの名前を再束縛することは不可能である(``global`` 宣言によって名前をグローバルスコープに強制することをのぞく) なので下記のコードは、ボタンをクリックすると表示された数値が増えたり減ったりすることを期待したコードだが、レキシカルスコープに慣れた人が期待するようには動かない。::

    def make_scoreboard(frame, score=0):
        label = Label(frame)
        label.pack()
        for i in [-10, -1, 1, 10]:
            def increment(step=i):
                score = score + step  # fails with UnboundLocalError
                label['text'] = score
            button = Button(frame, text='%+d' % i, command=increment)
            button.pack()
        return label

他の言語での例

JavaScript, Perl, Scheme, Smalltalk, GNU C, C# 2.0

これらの言語はスコープを示すために変数の宣言を用いる。JavaScriptでは、字句的スコープの変数はキーワード ``var`` をつけて宣言される。宣言されていない変数名はグローバルと解釈される。Perlでは字句的スコープの変数はキーワード ``my`` をつけて宣言される。宣言されていない変数名はグローバルと解釈される。Schemeではすべての変数は(``define`` や ``let`` をつけるか、仮引数として)宣言が必要である。Smalltalkではブロックはすべて、縦棒で挟まれた、ローカル変数名を宣言するリストで始められる。CとC#ではすべての変数に型宣言が必要である。これらすべてにおいて変数はその宣言が含まれているスコープに属する。

Ruby1.8

Rubyは参考になる例だ。なぜなら他の現在有名な言語で唯一、Pythonのように、変数宣言を要求することなく静的にネストしたスコープをサポートすることに挑戦しているように見える。そしてそれゆえに一般的でない解決方法に到達したからだ。Rubyの関数は他の関数定義をもつことができる。そして波カッコで囲われたブロックを持つことができる。ブロックは外のスコープにアクセスすることが出来る。しかしネストした関数はそうではない。ブロックの中での名前への代入は、外のスコープですでに束縛された名前を隠さない場合に限りローカル変数の宣言と解釈される。そうでない場合は代入は外の名前の再束縛と解釈される。Rubyのスコープに関する構文とルールも長きに渡って議論されており、Ruby 2.0で変更される見込みである。 [28]_