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