Python は、さまざまなスコープのディクショナリにすべてを保持するという点で少し奇妙です。元の a、b、c は最上位のスコープにあり、最上位の辞書にあります。関数には独自の辞書があります。print(a)
andステートメントに到達するとprint(b)
、その名前の辞書には何もないため、Python はリストを検索してグローバル ディクショナリでそれらを見つけます。
これc+=1
はもちろん と同等c=c+1
です。Python がその行をスキャンすると、「ああ、c という名前の変数があります。ローカル スコープの辞書に入れます」と表示されます。次に、割り当ての右側にある c の c の値を探しに行くと、 cという名前のローカル変数が見つかりますが、まだ値がないため、エラーがスローされます。
上記のステートメントは、グローバル スコープの を使用するため、新しいスコープは必要ないglobal c
ことをパーサーに伝えているだけです。c
行に問題があると言う理由は、コードを生成しようとする前に名前を効果的に探しているためであり、ある意味ではまだその行を実際に実行しているとは考えていません。これは使いやすさのバグだと思いますが、一般的には、コンパイラのメッセージをあまり真剣に受け止めないように学習することをお勧めします。
慰めになるとすれば、この同じ問題を掘り下げて実験するのにおそらく 1 日を費やした後、Guido がすべてを説明した辞書について書いたものを見つけることができました。
更新、コメントを参照してください:
コードを 2 回スキャンすることはありませんが、字句解析と解析の 2 つのフェーズでコードをスキャンします。
このコード行の解析がどのように機能するかを考えてみましょう。lexer はソース テキストを読み取り、文法の「最小コンポーネント」である語彙素に分割します。だからそれがラインに当たったとき
c+=1
それはそれを次のようなものに分割します
SYMBOL(c) OPERATOR(+=) DIGIT(1)
パーサーは最終的にこれを解析木にして実行したいのですが、代入なのでその前にローカル辞書で c という名前を探し、見つからず、辞書に挿入し、マークを付けます初期化されていません。完全にコンパイルされた言語では、シンボルテーブルに入って解析を待つだけですが、2回目のパスの余裕がないため、レクサーは後で作業を楽にするために少し余分な作業を行います. それからのみ、それは OPERATOR を見て、ルールが「演算子 += がある場合、左辺は初期化されているに違いない」と述べていることを確認し、「おっと!」と言います。
ここでのポイントは、実際にはまだ行の解析を開始していないということです。これはすべて、実際の解析の準備として行われているため、行カウンターは次の行に進んでいません。したがって、エラーを通知するとき、それはまだ前の行にあると考えています。
私が言ったように、これはユーザビリティのバグだと主張することができますが、実際にはかなり一般的なことです。一部のコンパイラはそれについてより正直で、「XXX 行またはその周辺でエラーが発生しました」と言っていますが、これはそうではありません。