次のコードは、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)a
とb
の値は正しく印刷されます。これには、2つの理由で完全に困惑させられます。
行(B)の後のステートメントが原因で、行(A)で実行時エラーがスローされるのはなぜですか?
変数a
とb
が期待どおりに印刷されるのに、なぜc
はエラーを発生させるのですか?
私が思いつく唯一の説明は、local変数c
が割り当てc+=1
は、ローカル変数が作成される前であっても、「グローバル」変数c
よりも優先されます。もちろん、変数が存在する前にスコープを「盗む」ことは意味がありません。
誰かがこの動作を説明してもらえますか?
Pythonは、関数内から変数に値を割り当てるかどうかに応じて、関数内の変数を異なる方法で処理します。関数に変数への割り当てが含まれている場合、デフォルトでローカル変数として扱われます。したがって、行のコメントを解除すると、値が割り当てられる前にローカル変数を参照しようとしています。
変数c
にグローバルc
putを参照させたい場合
global c
関数の最初の行として。
python 3については、
nonlocal c
c
変数を持つ最も近い囲み関数スコープを参照するために使用できます。
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またはその周辺のエラー」と言いますが、これはしません。
分解を見ると、何が起こっているのかが明確になる場合があります。
>>> 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'の値を取得して出力する」として、この変数がまだ初期化されていないことを検出すると、例外を発生させます。
Pythonは、従来のグローバル変数セマンティクスを試してみると、かなり興味深い動作をします。詳細は覚えていませんが、 'global'スコープで宣言された変数の値は問題なく読み取ることができますが、変更する場合はglobal
キーワードを使用する必要があります。 test()
を次のように変更してみてください:
def test():
global c
print(a)
print(b)
print(c) # (A)
c+=1 # (B)
また、このエラーが発生する理由は、関数内で「グローバル」変数と同じ名前で新しい変数を宣言することもでき、完全に分離されるためです。インタープリターは、このスコープでc
という新しい変数を作成し、1回の操作ですべて変更しようとしていますが、この新しいc
は初期化されていないため、Pythonでは許可されません。
それを明確にする最良の例は次のとおりです。
_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変数のスコープの完全な説明と分析:
ここに役立つ2つのリンクがあります
リンク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)
これはあなたの質問に対する直接的な答えではありませんが、拡張された割り当てと関数スコープの関係によって引き起こされる別の落とし穴であるため、密接に関連しています。
ほとんどの場合、拡張された割り当て(_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
_
そのため、トリックは関数の引数への拡張代入を避けることです(ローカル/ループ変数にのみ使用しようとしています)。単純な割り当てを使用すると、あいまいな動作から安全になります。
Pythonインタープリターは関数を完全なユニットとして読み取ります。1回はクロージャー(ローカル変数)を収集し、再びバイトに変換するために2回のパスで読み取ります) -コード。
既にご存知のとおり、「=」の左側で使用される名前はすべて暗黙的にローカル変数です。変数アクセスを+ =に変更することで何度もキャッチされましたが、突然異なる変数になりました。
また、具体的にはグローバルスコープとは何の関係もありません。ネストされた関数でも同じ動作をします。
c+=1
はc
を割り当て、pythonは割り当てられた変数がローカルであると仮定しますが、この場合はローカルで宣言されていません。
global
またはnonlocal
キーワードを使用します。
nonlocal
は、python 3でのみ動作します。したがって、python 2を使用していて、変数をグローバルにしたくない場合は、可変オブジェクトを使用できます:
my_variables = { # a mutable object
'c': 3
}
def test():
my_variables['c'] +=1
test()
クラス変数に到達する最良の方法は、クラス名で直接アクセスすることです
class Employee:
counter=0
def __init__(self):
Employee.counter+=1
pythonでは、ローカル変数、クラス変数、グローバル変数のすべてのタイプの変数について同様の宣言があります。メソッドからグローバル変数を参照する場合、python実際には、まだ定義されていないためエラーをスローするメソッド自体から変数を参照していますグローバル変数を参照するには、globals()['variableName']を使用する必要があります。
あなたの場合、それぞれa、bとcの代わりにglobals()['a]、globals()[' b ']とglobals()[' c ']を使用してください。