10

Pythonでは、次のように書くことができます:

def func():
    x = 1
    print x
    x+=1

    def _func():
        print x
    return _func

test = func()
test()

実行すると、出力は次のようになります。

1

2

_func は、func で定義された "x" 変数にアクセスできるためです。右...

しかし、もしそうなら:

def func():
    x = 1
    print x

    def _func():
        x+=1
        print x
    return _func

test = func()
test()

次に、エラー メッセージが表示されました: UnboundLocalError: 代入前に参照されたローカル変数 'x'

この場合、_func は「x」変数を「見る」ことができないようです。

問題は、数学演算子 x+=1 が例外をスローするのに、最初の例の print x が「x」変数を「見る」のはなぜですか?

理由がわかりません...

4

3 に答える 3

3

Python が「解釈された」言語であることを考慮すると、変数が外側のスコープで既に定義されている場合、同じ変数は内側のスコープでもアクセスできるはずであると自然に想定されます。実際、最初の例では、内側が_func単に印刷xされるだけで機能します。

ただし、Python について明らかでないことは、変数のスコープが正確に決定される方法ではないということです。Python はコンパイル時に、変数がそのスコープ内で割り当てられているかどうかに基づいて、スコープに対して「ローカル」と見なすべき変数を分析します。この場合、代入には増補代入演算子が含まれます。したがって、Python_funcが 2 番目の例のインナーをバイトコードにコンパイルするとき、 を見て、それがへのローカル変数である必要があるx += 1と判断します。もちろん、最初の代入は増補代入であるため、ローカルに増補する変数はなく、.x_funcxUnboundLocalError

これを別の方法で確認するには、次のように記述します。

def _func():
    print x
    x = 2

ここでも、外部関数で定義されたものとしてではなく、その関数のスコープ内のローカル変数として扱う_func行が含まれているためです。したがって、も になります。x = 2xxprint xUnboundLocalError

disモジュールを使用して関数用に生成されたバイトコードを表示することで、これをより詳細に調べることができます。

>>> dis.dis(_func)
  2           0 LOAD_FAST                0 (x)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  3           5 LOAD_CONST               1 (2)
              8 STORE_FAST               0 (x)
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE

オペコードは、LOAD_FAST低速でより一般的な名前のルックアップをバイパスするローカル変数の「高速」ルックアップを目的としています。辞書検索などを行うのではなく、各ローカル変数 (現在のスタック フレーム内) がその配列内のインデックスに関連付けられているポインターの配列を使用します。LOAD_FAST上記の例では、オペコードへの唯一の引数は --0この場合、最初の (そして唯一の) ローカルです。

関数自体 (具体的にはその基になるコード オブジェクト) で、そのコードで使用されているローカル変数が 1 つあり、それに関連付けられている変数名が次のとおりであることを確認できます'x'

>>> _func.__code__.co_nlocals
1
>>> _func.__code__.co_varnames
('x',)

それdis.disが報告できる方法です0 LOAD_FAST 0 (x)STORE_FAST後のオペコードについても同様です。

Python で変数のスコープを理解するためにこれを知る必要はありませんが、内部で何が起こっているかを知ることは役に立ちます。

他のいくつかの回答で既に述べたように、Python 3nonlocalは、ローカル スコープ内のその変数への割り当てに基づいて、このコンパイル時の名前のローカル変数へのバインドを防止するキーワードを導入しました。

于 2013-09-04T14:22:34.010 に答える
0

Python 2 で閉じられた変数を再バインドすることはできません ( nonlocalPython 3 で使用できます)。Python 2 で行っていることを実行する必要がある場合は、次の回避策を実行します。

class Pointer(object):
    def __init__(self, val):
        self.val = val

def func():
    px = Pointer(1)
    print px.val
    px.val += 1

    def _func():
        print px.val
    return _func

test = func()
test()

基本的に、変更する必要のある値をオブジェクトに入れます。1 つの要素を持つリストを使用することもあります。その後、コードを書き直して、メソッド呼び出しだけが発生するようにします。上記は実際には次と同等です。

class Pointer(object):
    def __init__(self, val):
        self.val = val

def func():
    px = Pointer(1)
    print px.val
    px.__setattr__('val', px.val + 1)

    def _func():
        print px.val
    return _func

test = func()
test()
于 2013-09-04T14:17:31.187 に答える