2009-10-12 65 views
60

Digamos que tengo la siguiente estructura de directorios:circular dependencia de las importaciones en Python

a\ 
    __init__.py 
    b\ 
     __init__.py 
     c\ 
      __init__.py 
      c_file.py 
     d\ 
      __init__.py 
      d_file.py 

En el paquete de a__init__.py, el paquete c es importado. Pero c_file.py importa a.b.d.

El programa falla, diciendo que b no existe cuando c_file.py intenta importar a.b.d. (Y realmente no existe, porque estábamos en el medio de importarlo.)

¿Cómo se puede solucionar este problema?

+1

¿Tal vez podría probar las importaciones relativas? http://stackoverflow.com/questions/72852/how-to-do-relative-imports-in-python – eremzeit

+1

esto puede ayudar a https://ncoghlan_devs-python-notes.readthedocs.org/en /latest/python_concepts/import_traps.html – maazza

+0

también como referencia, parece que las importaciones circulares están permitidas en Python 3.5 (y probablemente más allá) pero no 3.4 (y probablemente más abajo). –

Respuesta

53

Si a depende de c y c depende de a, ¿no son en realidad la misma unidad, entonces?

realmente se debe examinar por qué ha dividido a y c en dos paquetes, ya sea porque tienes algo de código que debe dividir apagado en otro paquete (para que sean tanto dependen de que el nuevo paquete, pero no entre sí), o deberías unirlos en un solo paquete.

+83

Sí, podrían considerarse el mismo paquete. Pero si esto resulta en un archivo enormemente grande, entonces no es práctico. Estoy de acuerdo en que con frecuencia, las dependencias circulares significan que el diseño debe pensarse nuevamente. Pero existen algunos patrones de diseño donde es apropiado (y donde fusionar los archivos juntos podría dar como resultado un archivo enorme), así que creo que es dogmático decir que los paquetes deben combinarse o el diseño debe volver a evaluarse. –

140

Puede posponer la importación, por ejemplo, en a/__init__.py:

def my_function(): 
    from a.b.c import Blah 
    return Blah() 

es decir, aplazar la importación hasta que sea realmente necesario. Sin embargo, también vería de cerca las definiciones/usos de mi paquete, ya que una dependencia cíclica como la señalada podría indicar un problema de diseño.

+5

Me alegro de que haya señalado esto, esto funcionó perfectamente para mí. Consideré cuidadosamente mi diseño y pensé que en este caso, es bueno. –

+3

A veces las referencias circulares son realmente inevitables. Este es el único enfoque que funciona para mí en estas circunstancias. –

+1

¿No agregaría esto un montón de sobrecarga en cada llamada de foo? –

-3

Otra solución es usar un proxy para el d_file.

Por ejemplo, supongamos que quiere compartir la clase blah con el c_file. Así pues, el d_file contiene:

class blah: 
    def __init__(self): 
     print("blah") 

Esto es lo que introduzca en c_file.py:

# do not import the d_file ! 
# instead, use a place holder for the proxy of d_file 
# it will be set by a's __init__.py after imports are done 
d_file = None 

def c_blah(): # a function that calls d_file's blah 
    d_file.blah() 

Y en un init es .py:

from b.c import c_file 
from b.d import d_file 

class Proxy(object): # module proxy 
    pass 
d_file_proxy = Proxy() 
# now you need to explicitly list the class(es) exposed by d_file 
d_file_proxy.blah = d_file.blah 
# finally, share the proxy with c_file 
c_file.d_file = d_file_proxy 

# c_file is now able to call d_file.blah 
c_file.c_blah() 
+11

modificando los atributos del módulo global en un archivo diferente como el que rápidamente conducirá a una pesadilla – Antimony

19

me he preguntado esto un par de veces (generalmente cuando se trata de modelos que necesitan conocerse entre ellos). La solución simple es solo importar todo el módulo, luego hacer referencia a lo que necesita.

Así que en lugar de hacer

from models import Student 

en uno, y

from models import Classroom 

en la otra, acaba de hacer

import models 

en uno de ellos, a continuación, llamar al models.Classroom lo necesita.

+1

Elegante y brillante !! – nish

0

El problema es que cuando se ejecuta desde un directorio, de manera predeterminada solo los paquetes que son subdirectorios son visibles como importaciones de candidatos, por lo que no puede importar a.b.d. Sin embargo, puede importar b.d. ya que b es un sub paquete de a.

Si realmente desea importar a.b.d en c/__init__.py puede lograr esto cambiando la ruta del sistema para que sea un directorio sobre a y cambie la importación en a/__init__.py para importar a.b.c.

Su a/__init__.py debería tener este aspecto: surge

import sys 
import os 
# set sytem path to be directory above so that a can be a 
# package namespace 
DIRECTORY_SCRIPT = os.path.dirname(os.path.realpath(__file__)) 
sys.path.insert(0,DIRECTORY_SCRIPT+"/..") 
import a.b.c 

Una dificultad adicional cuando se desea ejecutar módulos en C como guiones. Aquí los paquetes a y b no existen. Puede hackear el __int__.py en el directorio c para apuntar sys.path al directorio de nivel superior y luego importar __init__ en cualquier módulo dentro de c para poder usar la ruta completa para importar a.b.d. Dudo que sea una buena práctica importar __init__.py, pero ha funcionado para mis casos de uso.

Cuestiones relacionadas