Você está usando from bar import a. atorna-se um símbolo no escopo global do módulo de importação (ou em qualquer escopo em que a instrução de importação ocorra).
Ao atribuir um novo valor a a, você está apenas alterando os apontos de valor também, não o valor real. Tente importar bar.pydiretamente com o import barin __init__.pye conduzir seu experimento lá definindo bar.a = 1. Desta forma, você estará realmente modificando bar.__dict__['a']qual é o valor 'real' de aneste contexto.
É um pouco complicado com três camadas, mas bar.a = 1muda o valor de ano módulo chamado de barque é realmente derivado __init__.py. Não altera o valor do aque foobarvê porque foobarreside no arquivo real bar.py. Você pode definir bar.bar.ase quiser mudar isso.
Este é um dos perigos de usar a from foo import barforma da importinstrução: ela se divide barem dois símbolos, um visível globalmente de dentro do fooqual começa apontando para o valor original e um símbolo diferente visível no escopo onde a importinstrução é executada. Alterar um ponto para onde um símbolo aponta não altera o valor que ele apontou também.
Esse tipo de coisa é um assassino ao tentar reloadum módulo do interpretador interativo.
Obrigado! Sua resposta provavelmente me salvou várias horas de procurar o culpado.
jnns
Parece que até mesmo a bar.bar.aabordagem não ajudará muito na maioria dos casos de uso. Provavelmente é uma ideia fraca misturar compilação ou carregamento de módulo e atribuições de tempo de execução porque você acabará se confundindo (ou outros que usam seu código). Especialmente em linguagens que se comportam de maneira um tanto inconsistente, como o python provavelmente faz.
matanster
26
Uma fonte de dificuldade com essa questão é que você tem um programa chamado bar/bar.py: import barimporta bar/__init__.pyou bar/bar.py, dependendo de onde é feito, o que torna um pouco complicado rastrear qual aé bar.a.
É assim que funciona:
A chave para entender o que acontece é perceber que em seu __init__.py,
from bar import a
na verdade, faz algo como
a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)
e define uma nova variável ( bar/__init__.py:ase desejar). Assim, seu from bar import ain __init__.pyassocia o nome bar/__init__.py:aao bar.py:aobjeto original ( None). É por isso que você pode fazer from bar import a as a2em __init__.py: neste caso, é claro que você tem ambos bar/bar.py:ae um nome de variável distintobar/__init__.py:a2 (no seu caso, os nomes das duas variáveis são ambos a, mas ainda vivem em namespaces diferentes: em __init__.py, eles são bar.ae a).
Agora, quando você fizer
import bar
print bar.a
você está acessando a variável bar/__init__.py:a(já que import barimporta o seu bar/__init__.py). Esta é a variável que você modifica (para 1). Você não está tocando o conteúdo da variável bar/bar.py:a. Então, quando você posteriormente fizer
bar.foobar()
você chama bar/bar.py:foobar(), que acessa a variável ade bar/bar.py, que ainda é None(quando foobar()é definida, vincula nomes de variáveis de uma vez por todas, então o ain bar.pyé bar.py:a, não qualquer outra avariável definida em outro módulo - como pode haver muitas avariáveis em todos os módulos importados ) Daí a última Nonesaída.
Conclusão: é melhor evitar qualquer ambigüidade no import bar, por não ter nenhum bar/bar.pymódulo (já que bar.__init__.pytorna o diretório bar/um pacote, com o qual você também pode importar import bar).
Em outras palavras: Acontece que esse equívoco é muito fácil de fazer.
É definido sorrateiramente na referência da linguagem Python: o uso de objeto em vez de símbolo . Eu sugeriria que a referência da linguagem Python torne isso mais claro e menos esparso.
O fromformulário não vincula o nome do módulo: ele percorre a lista de identificadores, procura cada um deles no módulo encontrado na etapa (1) e vincula o nome no namespace local ao objeto assim encontrado.
CONTUDO:
Ao importar, você importa o valor atual do símbolo importado e o adiciona ao seu namespace conforme definido. Você não está importando uma referência, está efetivamente importando um valor.
Portanto, para obter o valor atualizado de i, você deve importar uma variável que contém uma referência a esse símbolo.
Em outras palavras, importar NÃO é como uma declaração em importJAVA, uma externaldeclaração em C / C ++ ou mesmo uma usecláusula em PERL.
Em vez disso, a seguinte instrução em Python:
from some_other_module import a as x
é mais parecido com o seguinte código em K&R C:
extern int a;/*importfrom the EXTERN file */
int x = a;
(advertência: no caso do Python, "a" e "x" são essencialmente uma referência ao valor real: você não está copiando o INT, está copiando o endereço de referência)
Na verdade, eu acho o jeito do Python importmuito mais limpo do que o do Java, porque namespaces / escopos devem sempre ser mantidos devidamente separados e nunca interferir uns com os outros de maneiras inesperadas. Como aqui: Alterar uma ligação de um objeto a um nome em um namespace (leia: atribuir algo à propriedade global de um módulo) nunca afeta outros namespaces (leia: a referência que foi importada) no Python. Mas faz isso em Java, etc. Em Python você só precisa entender o que é importado, enquanto em Java, você também deve entender o outro módulo, caso ele altere esse valor importado posteriormente.
Tino
2
Eu tenho que discordar fortemente. Importar / incluir / usar tem um longo histórico de uso do formulário de referência e não do formulário de valor apenas em relação a todos os outros idiomas.
Mark Gerolimatos
4
Droga, apertei return… existe essa expectativa de importação por referência (conforme @OP). Na verdade, um novo programador Python, não importa o quão experiente, deve ser informado sobre isso do tipo "olhar para fora". Isso nunca deveria acontecer: com base no uso comum, Python escolheu o caminho errado. Crie um "valor de importação" se necessário, mas não confunda símbolos com valores no momento da importação.
bar.bar.a
abordagem não ajudará muito na maioria dos casos de uso. Provavelmente é uma ideia fraca misturar compilação ou carregamento de módulo e atribuições de tempo de execução porque você acabará se confundindo (ou outros que usam seu código). Especialmente em linguagens que se comportam de maneira um tanto inconsistente, como o python provavelmente faz.Uma fonte de dificuldade com essa questão é que você tem um programa chamado
bar/bar.py
:import bar
importabar/__init__.py
oubar/bar.py
, dependendo de onde é feito, o que torna um pouco complicado rastrear quala
ébar.a
.É assim que funciona:
A chave para entender o que acontece é perceber que em seu
__init__.py
,na verdade, faz algo como
e define uma nova variável (
bar/__init__.py:a
se desejar). Assim, seufrom bar import a
in__init__.py
associa o nomebar/__init__.py:a
aobar.py:a
objeto original (None
). É por isso que você pode fazerfrom bar import a as a2
em__init__.py
: neste caso, é claro que você tem ambosbar/bar.py:a
e um nome de variável distintobar/__init__.py:a2
(no seu caso, os nomes das duas variáveis são ambosa
, mas ainda vivem em namespaces diferentes: em__init__.py
, eles sãobar.a
ea
).Agora, quando você fizer
você está acessando a variável
bar/__init__.py:a
(já queimport bar
importa o seubar/__init__.py
). Esta é a variável que você modifica (para 1). Você não está tocando o conteúdo da variávelbar/bar.py:a
. Então, quando você posteriormente fizervocê chama
bar/bar.py:foobar()
, que acessa a variávela
debar/bar.py
, que ainda éNone
(quandofoobar()
é definida, vincula nomes de variáveis de uma vez por todas, então oa
inbar.py
ébar.py:a
, não qualquer outraa
variável definida em outro módulo - como pode haver muitasa
variáveis em todos os módulos importados ) Daí a últimaNone
saída.Conclusão: é melhor evitar qualquer ambigüidade no
import bar
, por não ter nenhumbar/bar.py
módulo (já quebar.__init__.py
torna o diretóriobar/
um pacote, com o qual você também pode importarimport bar
).fonte
Em outras palavras: Acontece que esse equívoco é muito fácil de fazer. É definido sorrateiramente na referência da linguagem Python: o uso de objeto em vez de símbolo . Eu sugeriria que a referência da linguagem Python torne isso mais claro e menos esparso.
CONTUDO:
Ao importar, você importa o valor atual do símbolo importado e o adiciona ao seu namespace conforme definido. Você não está importando uma referência, está efetivamente importando um valor.
Portanto, para obter o valor atualizado de
i
, você deve importar uma variável que contém uma referência a esse símbolo.Em outras palavras, importar NÃO é como uma declaração em
import
JAVA, umaexternal
declaração em C / C ++ ou mesmo umause
cláusula em PERL.Em vez disso, a seguinte instrução em Python:
é mais parecido com o seguinte código em K&R C:
(advertência: no caso do Python, "a" e "x" são essencialmente uma referência ao valor real: você não está copiando o INT, está copiando o endereço de referência)
fonte
import
muito mais limpo do que o do Java, porque namespaces / escopos devem sempre ser mantidos devidamente separados e nunca interferir uns com os outros de maneiras inesperadas. Como aqui: Alterar uma ligação de um objeto a um nome em um namespace (leia: atribuir algo à propriedade global de um módulo) nunca afeta outros namespaces (leia: a referência que foi importada) no Python. Mas faz isso em Java, etc. Em Python você só precisa entender o que é importado, enquanto em Java, você também deve entender o outro módulo, caso ele altere esse valor importado posteriormente.