¿Cómo cambiar una variable de módulo de otro módulo?

106

Supongamos que tengo un paquete llamado bary contiene bar.py:

a = None

def foobar():
    print a

y __init__.py:

from bar import a, foobar

Luego ejecuto este script:

import bar

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

Esto es lo que espero:

None
1
1

Esto es lo que obtengo:

None
1
None

¿Alguien puede explicar mi concepto erróneo?

shino
fuente

Respuestas:

103

Estás usando from bar import a. ase convierte en un símbolo en el ámbito global del módulo de importación (o cualquier ámbito en el que se produzca la declaración de importación).

Cuando asigna un nuevo valor a a, simplemente está cambiando qué apuntos de valor también, no el valor real. Trate de importar bar.pydirectamente con import baren __init__.pyy llevar a cabo el experimento no por el ajuste bar.a = 1. De esta manera, en realidad modificará bar.__dict__['a']cuál es el valor "real" de aen este contexto.

Es un poco complicado con tres capas, pero bar.a = 1cambia el valor de aen el módulo llamado barque en realidad se deriva __init__.py. No cambia el valor de lo aque foobarve porque foobarvive en el archivo real bar.py. Podrías establecer bar.bar.asi quisieras cambiar eso.

Este es uno de los peligros de usar la from foo import barforma de la importdeclaración: se divide baren dos símbolos, uno visible globalmente desde dentro del foocual comienza apuntando al valor original y un símbolo diferente visible en el alcance donde importse ejecuta la declaración. Cambiar a donde apunta un símbolo no cambia el valor al que apuntaba también.

Este tipo de cosas es un asesino cuando se trata de reloadun módulo del intérprete interactivo.

aaronasterling
fuente
¡Gracias! Tu respuesta probablemente me ahorró varias horas de buscar al culpable.
jnns
Parece que incluso el bar.bar.aenfoque no ayudará mucho en la mayoría de los casos de uso. Probablemente sea una idea débil mezclar compilación o carga de módulos y asignaciones de tiempo de ejecución porque terminará confundiéndose a sí mismo (u otros que usen su código). Especialmente en lenguajes que se comportan de manera algo inconsistente al respecto, como se podría decir que lo hace Python.
matanster
26

Una fuente de dificultad con esta pregunta es que tiene un programa llamado bar/bar.py: import barimporta bar/__init__.pyo bar/bar.py, dependiendo de dónde se haga, lo que hace que sea un poco engorroso rastrear cuál aes bar.a.

Así es como funciona:

La clave para comprender lo que sucede es darse cuenta de que en su __init__.py,

from bar import a

en efecto hace algo como

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

y define una nueva variable ( bar/__init__.py:a, si lo desea). Por lo tanto, su from bar import ain __init__.pyvincula el nombre bar/__init__.py:aal bar.py:aobjeto original ( None). Esta es la razón por la que puede hacerlo from bar import a as a2en __init__.py: en este caso, está claro que tiene ambos bar/bar.py:ay un nombre de variable distintobar/__init__.py:a2 (en su caso, los nombres de las dos variables resultan ser ambos a, pero aún viven en espacios de nombres diferentes: en __init__.py, son bar.ay a).

Ahora, cuando lo hagas

import bar

print bar.a

está accediendo a la variable bar/__init__.py:a(ya que import barimporta su bar/__init__.py). Esta es la variable que modificas (a 1). No está tocando el contenido de la variable bar/bar.py:a. Entonces, cuando lo hagas posteriormente

bar.foobar()

usted llama bar/bar.py:foobar(), que accede a la variable adesde bar/bar.py, que sigue siendo None(cuando foobar()se define, enlaza los nombres de las variables de una vez por todas, por lo que el ain bar.pyes bar.py:a, no cualquier otra avariable definida en otro módulo, ya que puede haber muchas avariables en todos los módulos importados ). De ahí la última Nonesalida.

Conclusión: es mejor evitar cualquier ambigüedad import baral no tener ningún bar/bar.pymódulo (ya bar.__init__.pyque el directorio ya es bar/un paquete, con el que también puedes importar import bar).

Eric O Lebigot
fuente
12

Dicho de otra manera: resulta que este error es muy fácil de hacer. Se define furtivamente en la referencia del lenguaje Python: el uso de objeto en lugar de símbolo . Sugeriría que la referencia del lenguaje Python lo haga más claro y menos disperso.

El fromformulario no vincula el nombre del módulo: recorre la lista de identificadores, busca cada uno de ellos en el módulo encontrado en el paso (1) y vincula el nombre en el espacio de nombres local al objeto así encontrado.

SIN EMBARGO:

Cuando importa, importa el valor actual del símbolo importado y lo agrega a su espacio de nombres según lo definido. No está importando una referencia, efectivamente está importando un valor.

Por lo tanto, para obtener el valor actualizado de i, debe importar una variable que contenga una referencia a ese símbolo.

En otras palabras, la importación NO es como una declaración en importJAVA, externalen C / C ++ o incluso una usecláusula en PERL.

Más bien, la siguiente declaración en Python:

from some_other_module import a as x

es más parecido al siguiente código en K&R C:

extern int a; /* import from the EXTERN file */

int x = a;

(advertencia: en el caso de Python, "a" y "x" son esencialmente una referencia al valor real: no está copiando el INT, está copiando la dirección de referencia)

Mark Gerolimatos
fuente
En realidad, encuentro que la forma de Python es importmucho más limpia que la de Java, porque los espacios de nombres / ámbitos siempre se mantendrán adecuadamente separados y nunca interferirán entre sí de formas inesperadas. Como aquí: alterar un enlace de un objeto a un nombre en un espacio de nombres (léase: asignar algo a la propiedad global de un módulo) nunca afecta a otros espacios de nombres (léase: la referencia que se importó) en Python. Pero lo hace en Java, etc. En Python solo necesitas entender lo que se importa, mientras que en Java, también debes entender el otro módulo en caso de que altera este valor importado más adelante.
Tino
2
Tengo que estar totalmente en desacuerdo. Importar / incluir / usar tiene una larga historia de uso del formulario de referencia y no del formulario de valor en todos los demás idiomas.
Mark Gerolimatos
4
Maldita sea, presioné regresar ... hay una expectativa de importación por referencia (según @OP). De hecho, a un nuevo programador de Python, sin importar la experiencia que tenga, se le tiene que decir esto de la manera de "mirar hacia afuera". Eso nunca debería suceder: según el uso común, Python tomó el camino equivocado. Cree un "valor de importación" si es necesario, pero no combine los símbolos con los valores en el momento de la importación.
Mark Gerolimatos