módulo reimportado si se importa desde una ruta diferente

En una aplicación grande en la que estoy trabajando, varias personas importan los mismos módulos de manera diferente, por ejemplo, import x o from y import x, los efectos secundarios de x se importan dos veces y pueden introducir errores muy sutiles, si alguien confía en los atributos globales

por ejemplo, supongamos que tengo un paquete mypakcage con tres archivos mymodule.py, main.py y init .py

mymodule.py contenidos

l = [] class A(object): pass 

contenidos main.py

 def add(x): from mypackage import mymodule mymodule.l.append(x) print "updated list",mymodule.l def get(): import mymodule return mymodule.l add(1) print "lets check",get() add(1) print "lets check again",get() 

se imprime

 updated list [1] lets check [] updated list [1, 1] lets check again [] 

porque ahora hay dos listas en dos módulos diferentes, de manera similar, la clase A es diferente. A mí me parece lo suficientemente serio porque las clases en sí se tratarán de manera diferente, por ejemplo, debajo del código se imprime Falso

 def create(): from mypackage import mymodule return mymodule.A() def check(a): import mymodule return isinstance(a, mymodule.A) print check(create()) 

Pregunta:

Hay alguna manera de evitar esto? Excepto la aplicación de ese módulo debe importarse de una manera onyl. Esto no puede ser manejado por el mecanismo de importación de Python, he visto varios errores relacionados con esto en el código django y en otros lugares también.

Solo puedo replicar esto si main.py es el archivo que realmente está ejecutando. En ese caso, obtendrá el directorio actual de main.py en la ruta del sistema. Pero aparentemente también tiene un conjunto de rutas del sistema para que se pueda importar el mypackage.

Python no se dará cuenta en esa situación de que mymodule y mypackage.mymodule es el mismo módulo, y obtienes este efecto. Este cambio ilustra esto:

 def add(x): from mypackage import mymodule print "mypackage.mymodule path", mymodule mymodule.l.append(x) print "updated list",mymodule.l def get(): import mymodule print "mymodule path", mymodule return mymodule.l add(1) print "lets check",get() add(1) print "lets check again",get() $ export PYTHONPATH=. $ python mypackage/main.py mypackage.mymodule path  mymodule path  

Pero agregue otro mainfile, en el directorio actual:

 realmain.py: from mypackage import main 

y el resultado es diferente:

 mypackage.mymodule path  mymodule path  

Entonces sospecho que tienes tu archivo principal de python dentro del paquete. Y en ese caso la solución es no hacer eso. 🙂

Cada espacio de nombres de módulos se importa una sola vez. El problema es que los estás importando de manera diferente. En la primera, está importando desde el paquete global, y en la segunda, está realizando una import local, no empaquetada. Python ve los módulos como diferentes. La primera importación se almacena internamente en caché como mypackage.mymodule y el segundo solo como mymodule .

Una forma de resolver esto es usar siempre las importaciones absolutas . Es decir, siempre dé a su módulo rutas de importación absolutas desde el paquete de nivel superior en adelante:

 def add(x): from mypackage import mymodule mymodule.l.append(x) print "updated list",mymodule.l def get(): from mypackage import mymodule return mymodule.l 

Recuerde que su punto de entrada (el archivo que ejecuta, main.py ) también debe estar fuera del paquete. Cuando desea que el código del punto de entrada esté dentro del paquete, por lo general, en su lugar, utiliza una secuencia de comandos pequeña. Ejemplo:

runme.py , fuera del paquete:

 from mypackage.main import main main() 

Y en main.py agregas:

 def main(): # your code 

Considero que este documento de Jp Calderone es un gran consejo sobre cómo (no) estructurar su proyecto de Python. Siguiendo no tendrás problemas. Preste atención a la carpeta bin – está fuera del paquete. Reproduciré todo el texto aquí:

Estructura del sistema de archivos de un proyecto Python

Hacer

  • Nombre el directorio algo relacionado con su proyecto. Por ejemplo, si su proyecto se llama ” Twisted “, nombre el directorio de nivel superior para sus archivos de origen Twisted . Cuando realice lanzamientos, debe incluir un sufijo de número de versión: Twisted-2.5 .
  • cree un directorio Twisted/bin y ponga sus ejecutables allí, si tiene alguno. No les dé una extensión .py , incluso si son archivos de origen de Python. No coloque ningún código en ellos excepto una importación y una llamada a una función principal definida en algún otro lugar de sus proyectos.
  • Si su proyecto se puede express como un único archivo fuente de Python, colóquelo en el directorio y asígnele un nombre relacionado con su proyecto. Por ejemplo, Twisted/twisted.py . Si necesita varios archivos de origen, cree un paquete en su lugar ( Twisted/twisted/ , con un Twisted/twisted/__init__.py ) y coloque sus archivos de origen en él. Por ejemplo, Twisted/twisted/internet.py .
  • ponga sus pruebas de unidad en un subpaquete de su paquete (nota: esto significa que la única opción de archivo fuente de Python anterior fue un truco; siempre necesita al menos otro archivo para sus pruebas de unidad). Por ejemplo, Twisted/twisted/test/ . Por supuesto, haz que sea un paquete con Twisted/twisted/test/__init__.py . Coloque las pruebas en archivos como Twisted/twisted/test/test_internet.py .
  • agregue Twisted/README y T wisted/setup.py para explicar e instalar su software, respectivamente, si se siente bien.

No hagas

  • ponga su fuente en un directorio llamado src o lib . Esto hace que sea difícil de ejecutar sin instalar.
  • ponga sus pruebas fuera de su paquete de Python. Esto hace que sea difícil ejecutar las pruebas en una versión instalada.
  • cree un paquete que solo tenga un __init__.py y luego coloque todo su código en __init__.py . Simplemente haga un módulo en lugar de un paquete, es más simple.
  • intente crear hacks mágicos para que Python pueda importar su módulo o paquete sin que el usuario agregue el directorio que lo contiene a su ruta de importación (ya sea a través de PYTHONPATH o algún otro mecanismo). No manejará correctamente todos los casos y los usuarios se enojarán con usted cuando su software no funcione en su entorno.