Python è un po 'strano in quanto mantiene tutto in un dizionario per i vari ambiti. Gli originali a, b, c si trovano nell'ambito più in alto e quindi in quel dizionario più in alto. La funzione ha il suo dizionario. Quando raggiungi le istruzioni print(a)
e print(b)
, non c'è nulla con quel nome nel dizionario, quindi Python cerca l'elenco e le trova nel dizionario globale.
Ora arriviamo a c+=1
, che è, ovviamente, equivalente a c=c+1
. Quando Python analizza quella linea, dice "aha, c'è una variabile chiamata c, la inserirò nel mio dizionario di ambito locale". Quindi, quando cerca un valore per c per la c sul lato destro dell'assegnazione, trova la sua variabile locale denominata c , che non ha ancora alcun valore e quindi genera l'errore.
L'affermazione di global c
cui sopra dice semplicemente al parser che utilizza l' c
ambito globale e quindi non ne ha bisogno di uno nuovo.
Il motivo per cui dice che c'è un problema sulla linea che fa è perché sta effettivamente cercando i nomi prima di provare a generare codice, e quindi in un certo senso non pensa che stia ancora facendo quella linea. Direi che si tratta di un bug di usabilità, ma in genere è una buona pratica imparare a non prendere troppo sul serio i messaggi di un compilatore .
Se è di conforto, ho probabilmente trascorso una giornata a scavare e sperimentare questo stesso problema prima di trovare qualcosa che Guido aveva scritto sui dizionari che spiegavano tutto.
Aggiorna, vedi commenti:
Non esegue la scansione del codice due volte, ma esegue la scansione del codice in due fasi, il lexing e l'analisi.
Considera come funziona l'analisi di questa riga di codice. Il lexer legge il testo sorgente e lo suddivide in lessici, i "componenti più piccoli" della grammatica. Quindi quando colpisce la linea
c+=1
lo spezza in qualcosa del genere
SYMBOL(c) OPERATOR(+=) DIGIT(1)
Il parser alla fine vuole trasformarlo in un albero di analisi ed eseguirlo, ma poiché è un compito, prima di farlo, cerca il nome c nel dizionario locale, non lo vede e lo inserisce nel dizionario, contrassegnandolo come non inizializzato. In un linguaggio completamente compilato, andrebbe semplicemente nella tabella dei simboli e aspetterebbe l'analisi, ma dal momento che NON avrà il lusso di un secondo passaggio, il lexer fa un piccolo lavoro extra per rendere la vita più facile in seguito. Solo, poi vede l'OPERATORE, vede che le regole dicono "se hai un operatore + = il lato sinistro deve essere stato inizializzato" e dice "whoops!"
Il punto qui è che non ha ancora veramente iniziato l'analisi della linea . Tutto ciò sta accadendo in una sorta di preparazione all'analisi effettiva, quindi il contatore della linea non è passato alla riga successiva. Pertanto, quando segnala l'errore, pensa ancora alla riga precedente.
Come ho detto, potresti obiettare che si tratta di un bug di usabilità, ma in realtà è una cosa abbastanza comune. Alcuni compilatori ne sono più onesti e dicono "errore sulla linea XXX o intorno", ma questo non lo fa.