2009-09-22 10 views
7

En una gran aplicación en la que estoy trabajando, varias personas importan los mismos módulos de manera diferente, p. importación x o de importación x los efectos secundarios Y de que es X ha sido importada dos veces y puede introducir errores muy sutiles, si alguien está confiando en atributos globalesmódulo reimportado si se importó de una ruta diferente

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

contenidos mymodule.py

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

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 Para mí, parece lo suficientemente grave porque las clases se tratarán de manera diferente p. a continuación código imprime falsos

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 one-way onyl. ¿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.

Respuesta

3

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

Python en esa situación no se dará cuenta 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 <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'> 
mymodule path <module 'mymodule' from '/tmp/mypackage/mymodule.pyc'> 

Pero añadir otra mainfile, en el directorio currect:

realmain.py: 
from mypackage import main 

y el resultado es diferente:

mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'> 
mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'> 

Así que sospecha que tiene su principal archivo python dentro del paquete. Y en ese caso, la solución es no hacer eso. :-)

+0

hmmm realidad que no es el caso, voy a ver cómo replicar con main.py paquete exterior –

+0

no tiene razón :) –

3

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

Una manera de resolver esto es a siempre use importaciones absolutas.Es decir, siempre dan su módulo de rutas absolutas de importación 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 se ejecuta, main.py) también debe ser fuera el paquete. Cuando desee que el código del punto de entrada esté dentro del paquete, generalmente utiliza una secuencia de comandos pequeña. Ejemplo:

runme.py, fuera del paquete:

from mypackage.main import main 
main() 

Y en main.py que añadir:

def main(): 
    # your code 

encuentro this document por JP Calderone para ser un gran consejo sobre la forma de la estructura (no) tu proyecto python. Siguiéndolo no tendrás problemas. Preste atención a la carpeta bin; está fuera del paquete. Reproduzco el texto completo aquí:

estructura del sistema de archivos de un proyecto de Python

Haz:

  • nombre del directorio algo relacionado con su proyecto. Por ejemplo, si su proyecto se llama "Twisted", nombre el directorio de nivel superior para sus archivos fuente Twisted. Cuando realice lanzamientos , debe incluir un sufijo de número de versión : Twisted-2.5.
  • crear un directorio Twisted/bin y poner sus ejecutables allí, si tiene cualquiera. No les dé una extensión .py , incluso si son archivos fuente de Python . No coloque ningún código en excepto una importación de y llame a una función principal definida en otro lugar en sus proyectos.
  • Si su proyecto es expresable como un solo archivo fuente de Python , luego ponerlo en el directorio y el nombre de algo relacionada con su proyecto. Por ejemplo, Twisted/twisted.py. Si necesita varios archivos fuente, crear un paquete lugar (Twisted/twisted/, con un vacío Twisted/twisted/__init__.py) y lugar los archivos de origen en el mismo. Para ejemplo, Twisted/twisted/internet.py.
  • poner las pruebas unitarias en un sub-conjunto de su paquete (nota - esto significa que la opción de archivo fuente de Python sola anteriormente era un truco - siempre se necesita en menos otro archivo para su unidad pruebas) Por ejemplo, Twisted/twisted/test/. Por supuesto, lo convierten en 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 y instale su software, respectivamente, si se siente bien.

No:

  • poner su origen en un directorio llamado src o lib. Esto hace que sea difícil de ejecutar sin instalar.
  • pon tus pruebas fuera de tu paquete Python . Esto dificulta ejecutar las pruebas con una versión instalada.
  • crea un paquete que solo tiene un __init__.py y luego coloca todo tu código en __init__.py. Simplemente haga un módulo en lugar de un paquete, es más simple.
  • tratar de llegar a hacks mágicas para hacer Python capaz de importar su módulo o paquete sin tener que el usuario añada el directorio que lo contiene a su ruta de importación (ya sea a través de PYTHONPATH o algún otro mecanismo de ). No manejará correctamente todas las cajas y los usuarios recibirán molestos cuando el software no funcione en su entorno.
Cuestiones relacionadas