circular-dependency - ¿Cómo evitar importaciones circulares en Python?




(4)

Considere el siguiente ejemplo de paquete python donde a.py y b.py dependen el uno del otro:

/package
    __init__.py
    a.py
    b.py

Hay varias formas de importar un módulo en python

import package.a           # Absolute import
import package.a as a_mod  # Absolute import bound to different name
from package import a      # Alternate absolute import
import a                   # Implicit relative import (deprecated, py2 only)
from . import a            # Explicit relative import

Desafortunadamente, solo la primera y la cuarta opción funcionan cuando tienes dependencias circulares (el resto todas aumentan ImportError o AttributeError ). En general, no debería usar la 4ta sintaxis, ya que solo funciona en python2 y corre el riesgo de chocar con otros módulos de terceros. Entonces, realmente, solo la primera sintaxis está garantizada para funcionar. Sin embargo, todavía tiene varias opciones cuando se trata de dependencias circulares.

EDITAR: Los problemas ImportError y AttributeError solo ocurren en python 2. En python 3 el mecanismo de importación ha sido reescrito y todas estas declaraciones de importación (con la excepción de 4) funcionarán, incluso con dependencias circulares.

Use Importaciones Absolutas

Solo use la primera sintaxis de importación arriba. La desventaja de este método es que los nombres de importación pueden ser muy largos para paquetes grandes.

En a.py

import package.b

En b.py

import package.a

Postergar importación hasta más tarde

He visto este método usado en muchos paquetes, pero todavía me parece raro, y no me gusta que no pueda ver la parte superior de un módulo y ver todas sus dependencias, tengo que ir a buscar a través de todas las funciones también.

En a.py

def func():
    from package import b

En b.py

def func():
    from package import a

Pon todas las importaciones en un módulo central

Esto también funciona, pero tiene el mismo problema que el primer método, donde todas las llamadas de paquetes y submódulos son muy largas . También tiene dos fallas principales: fuerza la importación de todos los submódulos , incluso si solo está utilizando uno o dos, y aún no puede ver ninguno de los submódulos y ver rápidamente sus dependencias en la parte superior, usted tiene que pasar a través de las funciones.

En __init__.py

from . import a
from . import b

En a.py

import package

def func():
    package.b.some_object()

En b.py

import package

def func():
    package.a.some_object()

Entonces esas son tus opciones (y todas chupan un poco de IMO). Francamente, esto parece ser un error evidente en la maquinaria de importación de pitón, pero esa es solo mi opinión.

Esta pregunta ya tiene una respuesta aquí:

Sé que el tema de las importaciones circulares en python ha aparecido muchas veces antes y he leído estas discusiones. El comentario que se hace repetidamente en estas discusiones es que una importación circular es un signo de un mal diseño y el código debe reorganizarse para evitar la importación circular.

¿Podría alguien decirme cómo evitar una importación circular en esta situación ?: Tengo dos clases y quiero que cada clase tenga un constructor (método) que toma una instancia de la otra clase y devuelve una instancia de la clase.

Más específicamente, una clase es mutable y una es inmutable. La clase inmutable es necesaria para hash, comparación, etc. La clase mutable es necesaria para hacer cosas también. Esto es similar a sets y frozensets o a listas y tuplas.

Podría poner ambas definiciones de clase en el mismo módulo. ¿Hay alguna otra sugerencia?

Un ejemplo de juguete sería la clase A que tiene un atributo que es una lista y la clase B que tiene un atributo que es una tupla. Entonces la clase A tiene un método que toma una instancia de clase B y devuelve una instancia de clase A (convirtiendo la tupla en una lista) y de forma similar la clase B tiene un método que toma una instancia de clase A y devuelve una instancia de clase B. (convirtiendo la lista en una tupla).


Solo importe el módulo, no lo importe desde el módulo:

Considere a.py :

import b

class A:
    def bar(self):
        return b.B()

y b.py :

import a

class B:
    def bar(self):
        return a.A()

Esto funciona perfectamente bien.


Hacemos una combinación de importaciones absolutas y funciones para una mejor lectura y cadenas de acceso más cortas.

  • Ventaja: cadenas de acceso más cortas en comparación con importaciones absolutas puras
  • Desventaja: un poco más de sobrecarga debido a la llamada de función adicional

main / sub / a.py

import main.sub.b
b_mod = lambda: main.sub.b

class A():
    def __init__(self):
        print('in class "A":', b_mod().B.__name__)

main / sub / b.py

import main.sub.a
a_mod = lambda: main.sub.a

class B():
    def __init__(self):
        print('in class "B":', a_mod().A.__name__)

La mayoría de las soluciones mostradas aquí implican carga dinámica. En su lugar, estaba buscando un compilador que reuniera todos los archivos dependientes en un único archivo de salida. De la misma forma que los preprocesadores Less / Sass ocupan de CSS @import at-rule. Como no encontré nada decente de este tipo, escribí una herramienta simple para resolver el problema.

Así que aquí está el compilador, https://github.com/dsheiko/jsic , que reemplaza $import("file-path") con el contenido del archivo solicitado de forma segura. Aquí está el complemento Grunt correspondiente: https://github.com/dsheiko/grunt-jsic .

En la rama maestra de jQuery, simplemente concatenan archivos fuente atómicos en uno solo, comenzando con intro.js y terminando con outtro.js . Eso no me conviene, ya que no proporciona flexibilidad en el diseño del código fuente. Echa un vistazo a cómo funciona con jsic:

src / main.js

var foo = $import("./Form/Input/Tel");

src / Form / Input / Tel.js

function() {
    return {
          prop: "",
          method: function(){}
    }
}

Ahora podemos ejecutar el compilador:

node jsic.js src/main.js build/mail.js

Y consigue el archivo combinado.

construir / main.js

var foo = function() {
    return {
          prop: "",
          method: function(){}
    }
};




python import circular-dependency