Como alterar uma variável de módulo de outro módulo?


106

Suponha que eu tenha um pacote chamado bare ele contenha bar.py:

a = None

def foobar():
    print a

e __init__.py:

from bar import a, foobar

Então eu executo este script:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

Aqui está o que espero:

None
1
1

Aqui está o que eu recebo:

None
1
None

Alguém pode explicar meu equívoco?

Respostas:


103

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).


12

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; /* import from 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.
Mark Gerolimatos
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.