2011-06-15 14 views
31

Supongamos que tenemos dos módulos con dependencias cíclicas:dependencias del módulo cíclicos y las importaciones relativas en Python

# a.py 
import b 
def f(): return b.y 
x = 42 

# b.py 
import a 
def g(): return a.x 
y = 43 

Los dos módulos están en el directorio pkg con un vacío __init__.py. Importar pkg.a o pkg.b funciona bien, como se explica en this answer. Si cambio de las importaciones de las importaciones en relación

from . import b 

consigo un ImportError al intentar importar uno de los módulos:

>>> import pkg.a 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "pkg/a.py", line 1, in <module> 
    from . import b 
    File "pkg/b.py", line 1, in <module> 
    from . import a 
ImportError: cannot import name a 

¿Por qué obtengo este error? ¿No es la situación más o menos la misma que la anterior? (¿Está relacionado con this question?)

Editar: Esta pregunta no se trata de diseño de software. Soy consciente de las formas de evitar la dependencia circular, pero de todos modos estoy interesado en el motivo del error.

+0

Puede especificar los detalles del 'ImportError' que está recibiendo al cambiar a' from. importar b'? –

+0

¿Cómo ejercitas este código? ¿Tiene otro módulo en 'pkg' que hace algo como:' if __name__ == 'main': from. importar a'? Si es así, le recomendamos que lea [PEP 366] (http://python.org/dev/peps/pep-0366/), este PEP facilita el uso de importaciones relativas desde módulos ejecutables con paquetes. –

+2

si __a__ depende de __b__ y __b__ depende de __a__ ¿por qué no unir ambos en el mismo archivo? – JBernardo

Respuesta

29

en primer lugar vamos a empezar con la forma from import trabajo en Python:

Bueno en primer lugar vamos a ver el código de bytes:

>>> def foo(): 
...  from foo import bar 

>>> dis.dis(foo) 
2   0 LOAD_CONST    1 (-1) 
       3 LOAD_CONST    2 (('bar',)) 
       6 IMPORT_NAME    0 (foo) 
       9 IMPORT_FROM    1 (bar) 
      12 STORE_FAST    0 (bar) 
      15 POP_TOP    
      16 LOAD_CONST    0 (None) 
      19 RETURN_VALUE   

hmm interesting :), entonces from foo import bar se traduce al primer IMPORT_NAME foo que equivale a import foo y luego IMPORT_FROM bar.

¿Ahora qué IMPORT_FROM hacer?

vamos a ver qué pitón hacer cuando se encontró IMPORT_FROM:

TARGET(IMPORT_FROM) 
    w = GETITEM(names, oparg); 
    v = TOP(); 
    READ_TIMESTAMP(intr0); 
    x = import_from(v, w); 
    READ_TIMESTAMP(intr1); 
    PUSH(x); 
    if (x != NULL) DISPATCH(); 
    break; 

Bueno, básicamente, él consiguió los nombres que desea importar, que está en nuestra función foo() será bar, entonces pop de la pila del marco el valor v que es el retorno del código de operación ejecutada la última, que es IMPORT_NAME, a continuación, llamar a la función import_from() con estos dos argumentos:

static PyObject * 
import_from(PyObject *v, PyObject *name) 
{ 
    PyObject *x; 

    x = PyObject_GetAttr(v, name); 

    if (x == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { 
     PyErr_Format(PyExc_ImportError, "cannot import name %S", name); 
    } 
    return x; 
} 

Como puede ver la función import_from() es muy fácil, intente primero obtener el atributo name del módulo v, si no existe, suba ImportError, más devuelva este atributo.

Ahora, ¿qué tiene que ver con la importación relativa?

La importación relativa bien como from . import b son equivalentes, por ejemplo, en el caso que está en la pregunta OP a from pkg import b.

¿Pero cómo sucede esto? Para entender esto, deberíamos echar un vistazo al módulo import.c de python, especialmente a la función get_parent(). Como puede ver, la función es larga y silenciosa para enumerarla aquí, pero en general lo que hace cuando ve una importación relativa es intentar reemplazar el punto . con el paquete principal dependiendo del módulo __main__, que es nuevamente de la pregunta OP es el paquete pkg.

Ahora vamos a poner todo junto y tratar de averiguar por qué terminamos con el comportamiento en la pregunta OP.

Para esto nos ayudará si podemos ver lo que python hacer al hacer importaciones, bueno, es nuestro día de suerte python venir ya con esta característica que se puede habilitar ejecutando en modo extra verbose -vv.

Así, utilizando la línea de comandos: python -vv -c 'import pkg.b':

Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 

import pkg # directory pkg 
# trying pkg/__init__.so 
# trying pkg/__init__module.so 
# trying pkg/__init__.py 
# pkg/__init__.pyc matches pkg/__init__.py 
import pkg # precompiled from pkg/__init__.pyc 
# trying pkg/b.so 
# trying pkg/bmodule.so 
# trying pkg/b.py 
# pkg/b.pyc matches pkg/b.py 
import pkg.b # precompiled from pkg/b.pyc 
# trying pkg/a.so 
# trying pkg/amodule.so 
# trying pkg/a.py 
# pkg/a.pyc matches pkg/a.py 
import pkg.a # precompiled from pkg/a.pyc 
# clear[2] __name__ 
# clear[2] __file__ 
# clear[2] __package__ 
# clear[2] __name__ 
# clear[2] __file__ 
# clear[2] __package__ 
... 
Traceback (most recent call last): 
    File "<string>", line 1, in <module> 
    File "pkg/b.py", line 1, in <module> 
    from . import a 
    File "pkg/a.py", line 2, in <module> 
    from . import a 
ImportError: cannot import name a 
# clear __builtin__._ 

hmm lo que acaba de ocurrir antes de que el ImportError?

primero)from . import a en pkg/b.py se llama, que se traduce como se explicó anteriormente para from pkg import a, que está de nuevo en bytecode es equivalente a import pkg; getattr(pkg, 'a'). Pero espere un minuto a ¿es también un módulo? Bueno, aquí viene la parte divertida si tenemos algo como from module|package import module, en este caso se producirá una segunda importación que es la importación del módulo en la cláusula de importación. Entonces, de nuevo en el ejemplo de OP ahora necesitamos importar pkg/a.py, y como saben, antes de nada, configuramos en nuestra sys.modules una clave para nuestro nuevo módulo que será pkg.a y luego continuamos nuestra interpretación del módulo pkg/a.py, pero antes del módulo pkg/a.py finalícela importando llame al from . import b.

Ahora llegado el Segunda) parte, pkg/b.py se importará y en ella a su vez será primer intento de import pkg que por pkg está ya importó lo que no hay una clave pkg en nuestra sys.modules sólo devolverá el valor de esa llave. Luego se import b establecer la clave pkg.b en sys.modules y comenzar la interpretación. ¡Y llegamos a esta línea from . import a!

Peropkg/a.py recordar que ya se importa lo que significa ('pkg.a' in sys.modules) == True por lo que la importación se saltará, y sólo el getattr(pkg, 'a') serán llamados, pero ¿qué pasará? Python no terminó de importar pkg/a.py!? Por lo tanto, solo se llamará a getattr(pkg, 'a'), y esto aumentará un AttributeError en la función import_from(), que se traducirá al ImportError(cannot import name a).

DISCLAIM: Este es mi propio esfuerzo para entender lo que está sucediendo dentro del intérprete, estoy muy lejos de ser un experto.

EDIT: esta pregunta reformulada porque cuando he intentado volver a leerlo me comentó que mi respuesta fue mal formulada, esperan ahora será más útil :)

+0

Gracias por la respuesta detallada y por mirar las fuentes. Todavía no estoy realmente satisfecho. Dos cosas me confunden: 1. En tu punto (1), se ejecuta 'from pkg import b'. ¿Por qué funciona esto incluso cuando 'b' obviamente aún no está en el espacio de nombres de' pkg'? ¿No es esta la misma situación que en el siguiente paso, 'from pkg import a'? 2. Las dependencias cíclicas de los módulos suelen funcionar porque el objeto de módulo vacío se inserta en 'sys.modules' antes de que se ejecute el cuerpo del módulo. ¿Por qué no se inserta el objeto de módulo vacío 'a' en' pkg' antes de que se ejecute el cuerpo del módulo? –

+0

@seven: 1. porque 'pkg.b' no es' sys.modules', entonces tenemos que importarlo primero establezca la entrada 'pkg.b' en' sys.modules' luego, después de la importación, tenga éxito agregue 'b' al espacio de nombres de pkg. 2. El módulo 'a' se inserta al final de la importación en el espacio de nombres del paquete porque si falla la importación de' a' (puede generar una excepción en su módulo y ver) no queremos que se cuelgue en el espacio de nombre de 'pkg'. Básicamente, la adición del módulo importado al espacio de nombres del paquete se realiza después del éxito de la importación. – mouad

+0

Gracias por todo el esfuerzo que puso en esta respuesta. Ahora deja muy claro lo que está sucediendo. –

4

(Incedentally, la importación relativa no importa. El uso de from pkg import ... revela la misma excepción.)

Creo que lo que está pasando aquí es que la diferencia entre from foo import bar y import foo.bar es que en el primero , el valor bar podría ser un módulo en el paquete foo o podría ser una variable en un módulo foo. En el segundo caso, no es válido para bar ser cualquier cosa menos un módulo/paquete.

Esto importaría porque si se sabe que la barra es un módulo, entonces el contenido de sys.modules es suficiente para llenarlo. Si puede ser una variable en el módulo foo, entonces el intérprete debe buscar los contenidos de foo, pero al importar foo, eso no sería válido; el módulo real no se ha completado aún.

En el caso de una importación relativo, entendemos from . import bar en el sentido de importar el módulo de barras del paquete que contiene el módulo actual, pero esto es realmente el azúcar solo sintáctica, el nombre . se traduce a un nombre completo y pasó a __import__(), y por lo tanto se ve por todo el mundo como el ambiguo from foo import bar

+0

Gracias por la respuesta. Desafortunadamente, no creo que esto lo explique. Si no fue válido mirar los contenidos de 'foo' mientras se está importando, también las importaciones no cíclicas deberían fallar: si' foo.bar' hace 'de foo import quux', e importo' foo.bar', el intérprete también tiene que mirar el contenido de 'foo' mientras se lo está importando, y esto funciona bien. ¿Estoy teniendo sentido? –

1

Como una nota adicional:

que tenía la estructura siguiente módulo:

base 
+guiStuff 
    -gui 
+databaseStuff 
    -db 
-basescript 

que quería ser capaz de ejecutar mi script por import base.basescript, sin embargo, esto ha fallado con un error ya que el archivo gui tenía una import base.databaseStuff.db que causó una importación de base. Dado que base solo se registró como __main__, provocó una segunda ejecución de las importaciones completas y el error anterior a menos que use una secuencia de comandos externa por encima de base importando base/basescript solo una vez. Para evitar esto pongo lo siguiente en mi script base:

if __name__ == '__main__' or \ 
    not '__main__' in sys.modules or \ 
    sys.modules['__main__'].__file__ != __file__: 
    #imports here 
Cuestiones relacionadas