web-dev-qa-db-ja.com

Python変数スコープエラー

次のコードは、Python 2.5および3.0の両方で期待どおりに機能します。

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

ただし、行(B)のコメントを解除すると、UnboundLocalError: 'c' not assigned(A)abの値は正しく印刷されます。これには、2つの理由で完全に困惑させられます。

  1. (B)の後のステートメントが原因で、行(A)で実行時エラーがスローされるのはなぜですか?

  2. 変数abが期待どおりに印刷されるのに、なぜcはエラーを発生させるのですか?

私が思いつく唯一の説明は、local変数cが割り当てc+=1は、ローカル変数が作成される前であっても、「グローバル」変数cよりも優先されます。もちろん、変数が存在する前にスコープを「盗む」ことは意味がありません。

誰かがこの動作を説明してもらえますか?

193
tba

Pythonは、関数内から変数に値を割り当てるかどうかに応じて、関数内の変数を異なる方法で処理します。関数に変数への割り当てが含まれている場合、デフォルトでローカル変数として扱われます。したがって、行のコメントを解除すると、値が割り当てられる前にローカル変数を参照しようとしています。

変数cにグローバルc putを参照させたい場合

global c

関数の最初の行として。

python 3については、

nonlocal c

c変数を持つ最も近い囲み関数スコープを参照するために使用できます。

201
recursive

Pythonは、さまざまなスコープのすべてを辞書に保持するという点で少し奇妙です。元のa、b、cは最上位のスコープにあるため、最上位の辞書にあります。この関数には独自の辞書があります。 print(a)およびprint(b)ステートメントに到達すると、辞書にはその名前の情報はないため、Pythonはリストを検索し、グローバル辞書。

これでc+=1に到達します。これは、もちろんc=c+1と同等です。 Pythonがその行をスキャンすると、「aha、cという名前の変数があります。ローカルスコープのディクショナリに格納します。」と表示されます。」割り当ての右側にあるcは、そのcという名前のローカル変数を見つけますが、これにはまだ値がないため、エラーがスローされます。

上記のステートメントglobal cは、グローバルスコープからのcを使用するため、新しいものを必要としないことをパーサーに単に伝えます。

それが行に問題があると言う理由は、コードを生成しようとする前に名前を効果的に探しているため、ある意味でまだその行を実際にやっているとは思わないからです。これはユーザビリティのバグだと思いますが、一般的にはコンパイラのメッセージtooを真剣に受け取らないことを学ぶのは良い習慣です。

快適であれば、おそらく1日を掘って、この同じ問題を実験してから、すべてを説明する辞書についてGuidoが書いたものを見つけました。

更新、コメントを参照:

コードを2回スキャンすることはありませんが、字句解析と解析の2つのフェーズでコードをスキャンします。

このコード行の解析がどのように機能するかを検討してください。字句解析プログラムは、ソーステキストを読み取り、それを文法の「最小コンポーネント」である語彙素に分割します。だから、それがラインにヒットしたとき

c+=1

それは次のようなものに分割します

SYMBOL(c) OPERATOR(+=) DIGIT(1)

パーサーは最終的にこれを解析ツリーに入れて実行したいのですが、それが割り当てであるため、実行する前にローカル辞書で名前cを探し、表示せず、辞書に挿入してマークします未初期化として。完全にコンパイルされた言語では、シンボルテーブルに移動して解析を待機しますが、2回目のパスの余裕がないため、レクサーは後で作業を楽にするために少し余分な作業を行います。のみ、その後、OPERATORが表示され、ルールに「演算子+ =がある場合、左側が初期化されている必要があります」と表示され、「whoops!」と表示されます。

ここでのポイントは、それが実際にはまだ行の解析を開始していないということです。これはすべて、実際の解析の準備のようなものなので、行カウンタは次の行に進みません。したがって、エラーを通知するとき、前の行にあると考えます。

私が言うように、それはユーザビリティのバグであると主張することができますが、実際にはかなり一般的なものです。一部のコンパイラーはそれについてもっと正直で、「行XXXまたはその周辺のエラー」と言いますが、これはしません。

69
Charlie Martin

分解を見ると、何が起こっているのかが明確になる場合があります。

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

ご覧のとおり、aにアクセスするためのバイトコードはLOAD_FAST、およびbの場合、LOAD_GLOBAL。これは、コンパイラがaが関数内に割り当てられていることを識別し、それをローカル変数として分類したためです。ローカルのアクセスメカニズムは、グローバルでは根本的に異なります。フレームの変数テーブルで静的にオフセットが割り当てられます。つまり、ルックアップは、グローバルの場合のより高価なdictルックアップではなく、クイックインデックスです。このため、Pythonはprint a行は「スロット0に保持されているローカル変数 'a'の値を取得して出力する」として、この変数がまだ初期化されていないことを検出すると、例外を発生させます。

43
Brian

Pythonは、従来のグローバル変数セマンティクスを試してみると、かなり興味深い動作をします。詳細は覚えていませんが、 'global'スコープで宣言された変数の値は問題なく読み取ることができますが、変更する場合はglobalキーワードを使用する必要があります。 test()を次のように変更してみてください:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

また、このエラーが発生する理由は、関数内で「グローバル」変数と同じ名前で新しい変数を宣言することもでき、完全に分離されるためです。インタープリターは、このスコープでcという新しい変数を作成し、1回の操作ですべて変更しようとしていますが、この新しいcは初期化されていないため、Pythonでは許可されません。

10
Mongoose

それを明確にする最良の例は次のとおりです。

_bar = 42
def foo():
    print bar
    if False:
        bar = 0
_

foo()を呼び出すと、これもraisesUnboundLocalErrorになりますが、_bar=0_行には到達しません。そのため、論理的にローカル変数を作成しないでください。

謎は「Pythonは解釈言語」であり、関数fooの宣言は単一のステートメント(つまり、複合ステートメント)、それは単にそれを愚かに解釈し、ローカルおよびグローバルスコープを作成します。そのため、barは実行前にローカルスコープで認識されます。

Formore examplesthis likeこの投稿を読む: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2 /

この投稿では、Python変数のスコープの完全な説明と分析:

6
Sahil kalra

ここに役立つ2つのリンクがあります

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

リンク1は、エラーUnboundLocalErrorについて説明しています。リンク2は、テスト関数の書き直しに役立ちます。リンク2に基づいて、元の問題は次のように書き換えられます。

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)
5
mcdon

これはあなたの質問に対する直接的な答えではありませんが、拡張された割り当てと関数スコープの関係によって引き起こされる別の落とし穴であるため、密接に関連しています。

ほとんどの場合、拡張された割り当て(_a += b_)は単純な割り当て(_a = a + b_)とまったく同じと考える傾向があります。ただし、ある場合には、これで問題が発生する可能性があります。説明させてください:

Pythonの単純な割り当ての仕組みは、aが関数に渡される場合(func(a)のように、Pythonは常に参照渡し)であることに注意してください) 、_a = a + b_は渡されたaを変更しません。代わりに、aへのローカルポインターを変更します。

ただし、_a += b_を使用する場合、次のように実装される場合があります。

_a = a + b
_

または時々(メソッドが存在する場合)次のように:

_a.__iadd__(b)
_

最初の場合(aがグローバルに宣言されていない限り)、aへの割り当ては単なるポインタ更新であるため、ローカルスコープの外側に副作用はありません。

2番目の場合、aは実際にそれ自体を変更するため、aへのすべての参照は変更されたバージョンを指します。これは、次のコードで示されています。

_def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1
_

そのため、トリックは関数の引数への拡張代入を避けることです(ローカル/ループ変数にのみ使用しようとしています)。単純な割り当てを使用すると、あいまいな動作から安全になります。

3
alsuren

Pythonインタープリターは関数を完全なユニットとして読み取ります。1回はクロージャー(ローカル変数)を収集し、再びバイトに変換するために2回のパスで読み取ります) -コード。

既にご存知のとおり、「=」の左側で使用される名前はすべて暗黙的にローカル変数です。変数アクセスを+ =に変更することで何度もキャッチされましたが、突然異なる変数になりました。

また、具体的にはグローバルスコープとは何の関係もありません。ネストされた関数でも同じ動作をします。

2
James Hopkin

c+=1cを割り当て、pythonは割り当てられた変数がローカルであると仮定しますが、この場合はローカルで宣言されていません。

globalまたはnonlocalキーワードを使用します。

nonlocalは、python 3でのみ動作します。したがって、python 2を使用していて、変数をグローバルにしたくない場合は、可変オブジェクトを使用できます:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()
2
Colegram

クラス変数に到達する最良の方法は、クラス名で直接アクセスすることです

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1
1
Harun ERGUL

pythonでは、ローカル変数、クラス変数、グローバル変数のすべてのタイプの変数について同様の宣言があります。メソッドからグローバル変数を参照する場合、python実際には、まだ定義されていないためエラーをスローするメソッド自体から変数を参照していますグローバル変数を参照するには、globals()['variableName']を使用する必要があります。

あなたの場合、それぞれa、bとcの代わりにglobals()['a]、globals()[' b ']とglobals()[' c ']を使用してください。

0
Santosh Kadam