O código a seguir funciona conforme o esperado no Python 2.5 e 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()
No entanto, quando descomente a linha (B) , recebo uma UnboundLocalError: 'c' not assigned
na linha (A) . Os valores de a
e b
são impressos corretamente. Isso me deixou completamente perplexo por dois motivos:
Por que há um erro de tempo de execução lançado na linha (A) devido a uma instrução posterior na linha (B) ?
Por que são variáveis
a
eb
impresso como esperado, enquantoc
gera um erro?
A única explicação que posso encontrar é que uma variável localc
é criada pela atribuição c+=1
, que tem precedência sobre a variável "global" c
mesmo antes de a variável local ser criada. Obviamente, não faz sentido que uma variável "roube" o escopo antes que ele exista.
Alguém poderia explicar esse comportamento?
Respostas:
O Python trata variáveis em funções de maneira diferente, dependendo se você atribui valores a elas de dentro ou de fora da função. Se uma variável é atribuída dentro de uma função, ela é tratada por padrão como uma variável local. Portanto, ao remover o comentário da linha, você está tentando fazer referência à variável local
c
antes que qualquer valor tenha sido atribuído a ela.Se você deseja que a variável
c
se refira ao globalc = 3
atribuído antes da função, coloquecomo a primeira linha da função.
Quanto ao python 3, existe agora
que você pode usar para se referir ao escopo da função de fechamento mais próximo que possui uma
c
variável.fonte
global
ounonlocal
force a atribuição global ou não local)O Python é um pouco estranho, pois mantém tudo em um dicionário para os vários escopos. Os originais a, b, c estão no escopo mais alto e, portanto, no dicionário mais alto. A função possui seu próprio dicionário. Quando você alcança as instruções
print(a)
eprint(b)
, não há nada com esse nome no dicionário; portanto, o Python pesquisa a lista e as encontra no dicionário global.Agora chegamos a
c+=1
, que é, é claro, equivalente ac=c+1
. Quando o Python verifica essa linha, ele diz "aha, há uma variável chamada c, eu a colocarei no meu dicionário de escopo local". Então, quando procura um valor para c para c no lado direito da atribuição, encontra sua variável local denominada c , que ainda não tem valor e, portanto, gera o erro.A declaração
global c
mencionada acima simplesmente informa ao analisador que ele usa oc
escopo global e, portanto, não precisa de um novo.A razão pela qual diz que existe um problema na linha é que ela está procurando efetivamente os nomes antes de tentar gerar código e, de alguma forma, ainda não acha que realmente está fazendo essa linha. Eu diria que é um bug de usabilidade, mas geralmente é uma boa prática apenas aprender a não levar as mensagens de um compilador muito a sério.
Para facilitar, passei provavelmente um dia cavando e experimentando esse mesmo problema antes de encontrar algo que Guido havia escrito sobre os dicionários que explicavam tudo.
Atualização, veja os comentários:
Ele não verifica o código duas vezes, mas verifica o código em duas fases, lexing e análise.
Considere como a análise dessa linha de código funciona. O lexer lê o texto original e o divide em lexemas, os "menores componentes" da gramática. Então, quando atinge a linha
divide em algo como
O analisador eventualmente quer transformar isso em uma árvore de análise e executá-lo, mas como é uma atribuição, antes disso, ele procura o nome c no dicionário local, não o vê e o insere no dicionário, marcando como não inicializado. Em uma linguagem totalmente compilada, ele simplesmente entra na tabela de símbolos e aguarda a análise, mas como não tem o luxo de um segundo passe, o lexer faz um trabalho extra para facilitar a vida mais tarde. Somente então ele vê o OPERADOR, vê que as regras dizem "se você tem um operador + = o lado esquerdo deve ter sido inicializado" e diz "gritos!"
O ponto aqui é que ele ainda não iniciou a análise da linha . Tudo isso está meio preparatório para a análise real, portanto o contador de linhas não avançou para a próxima linha. Assim, quando sinaliza o erro, ele ainda pensa que está na linha anterior.
Como eu disse, você pode argumentar que é um bug de usabilidade, mas na verdade é uma coisa bastante comum. Alguns compiladores são mais honestos e dizem "erro na linha XXX", mas esse não é o caso.
fonte
dict
, é internamente apenas uma matriz (locals()
preencherá umdict
para retornar, mas as alterações nele não criarão novolocals
). A fase de análise está localizando cada atribuição para um local e convertendo de nome para posição nessa matriz e usando essa posição sempre que o nome é referenciado. Na entrada da função, os locais sem argumentos são inicializados em um espaço reservadoUnboundLocalError
es acontecem quando uma variável é lida e seu índice associado ainda tem o valor do espaço reservado.Examinar a desmontagem pode esclarecer o que está acontecendo:
Como você pode ver, o bytecode para acessar a é
LOAD_FAST
e para bLOAD_GLOBAL
,. Isso ocorre porque o compilador identificou que a é atribuído à função e a classificou como uma variável local. O mecanismo de acesso para os locais é fundamentalmente diferente para os globais - eles recebem estaticamente um deslocamento na tabela de variáveis do quadro, o que significa que a pesquisa é um índice rápido, em vez da pesquisa de ditado mais cara do que os globais. Por isso, o Python está lendo aprint a
linha como "obtenha o valor da variável local 'a' mantida no slot 0 e imprima-a" e, quando detecta que essa variável ainda não foi inicializada, gera uma exceção.fonte
O Python possui um comportamento bastante interessante quando você tenta a semântica tradicional de variáveis globais. Não me lembro dos detalhes, mas você pode ler muito bem o valor de uma variável declarada no escopo 'global', mas se quiser modificá-la, use a
global
palavra - chave. Tente mudartest()
para isso:Além disso, o motivo pelo qual você está recebendo esse erro é porque você também pode declarar uma nova variável dentro dessa função com o mesmo nome de uma 'global', e ela seria completamente separada. O intérprete acha que você está tentando criar uma nova variável nesse escopo chamada
c
e modificá-la em uma única operação, o que não é permitido no Python porque essa novac
não foi inicializada.fonte
O melhor exemplo que deixa claro é:
ao chamar
foo()
, isso também aumenta,UnboundLocalError
embora nunca cheguemos à linhabar=0
; portanto, a variável local logicamente nunca deve ser criada.O mistério está em " Python é uma linguagem interpretada " e a declaração da função
foo
é interpretada como uma única declaração (isto é, uma declaração composta), apenas a interpreta de maneira tola e cria escopos locais e globais. Portanto,bar
é reconhecido no escopo local antes da execução.Para mais exemplos como este Leia este post: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Este post fornece uma Descrição Completa e Análises do Escopo do Python de variáveis:
fonte
Aqui estão dois links que podem ajudar
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
o link um descreve o erro UnboundLocalError. O link dois pode ajudar a reescrever sua função de teste. Com base no link dois, o problema original pode ser reescrito como:
fonte
Essa não é uma resposta direta à sua pergunta, mas está intimamente relacionada, pois é outro problema causado pelo relacionamento entre atribuição aumentada e escopos de função.
Na maioria dos casos, você tende a pensar em atribuição aumentada (
a += b
) como exatamente equivalente a tarefa simples (a = a + b
). É possível ter algum problema com isso, em um dos cantos. Deixe-me explicar:A maneira como a atribuição simples do Python funciona significa que, se
a
for passado para uma função (comofunc(a)
; observe que o Python sempre é passado por referência), elea = a + b
não modificará oa
que é passado. Em vez disso, apenas modificará o ponteiro local paraa
.Mas se você usar
a += b
, às vezes é implementado como:ou algumas vezes (se o método existir) como:
No primeiro caso (desde que
a
não seja declarado global), não há efeitos colaterais fora do escopo local, pois a atribuição aa
é apenas uma atualização de ponteiro.No segundo caso,
a
ele realmente se modificará, portanto, todas as referênciasa
apontarão para a versão modificada. Isso é demonstrado pelo seguinte código:Portanto, o truque é evitar a atribuição aumentada de argumentos de função (tento usá-lo apenas para variáveis locais / de loop). Use tarefas simples e estará protegido contra comportamentos ambíguos.
fonte
O intérprete Python lerá uma função como uma unidade completa. Penso nisso como leitura em duas passagens, uma vez para reunir seu fechamento (as variáveis locais) e depois novamente para transformá-lo em código de bytes.
Como tenho certeza de que você já sabia, qualquer nome usado à esquerda de um '=' é implicitamente uma variável local. Mais de uma vez fui pego alterando o acesso de uma variável para um + = e, de repente, ela é uma variável diferente.
Eu também queria ressaltar que não tem nada a ver com o escopo global especificamente. Você obtém o mesmo comportamento com funções aninhadas.
fonte
c+=1
atribuic
, o python assume que as variáveis atribuídas são locais, mas, neste caso, não foi declarado localmente.Use as palavras-chave
global
ounonlocal
.nonlocal
funciona apenas no python 3, portanto, se você estiver usando o python 2 e não quiser tornar sua variável global, poderá usar um objeto mutável:fonte
A melhor maneira de alcançar a variável de classe é acessando diretamente pelo nome da classe
fonte
Em python, temos uma declaração semelhante para todos os tipos de variáveis local, variável de classe e variáveis globais. Quando você se refere a variável global do método, o python acha que você está realmente referindo a variável do próprio método que ainda não está definida e, portanto, gera erro. Para se referir à variável global, precisamos usar globals () ['variableName'].
no seu caso, use globals () ['a], globals () [' b '] e globals () [' c '] em vez de a, bec, respectivamente.
fonte
O mesmo problema me incomoda. Usando
nonlocal
eglobal
pode resolver o problema.No entanto, atenção necessária para o uso de
nonlocal
, ele funciona para funções aninhadas. No entanto, em um nível de módulo, ele não funciona. Veja exemplos aqui.fonte