¿Cómo evitar las importaciones circulares en Python? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Sé que el tema de las importaciones circulares en python ha surgido 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 ser reorganizado 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 tiene 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 otra es inmutable. Se necesita la clase inmutable para hacer hachís, comparar y así sucesivamente. La clase mutable también es necesaria para hacer cosas. Esto es similar a los conjuntos y frozensets o 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. Luego la clase A tiene un método que toma una instancia de la clase B y devuelve una instancia de la clase A (convirtiendo la tupla en una lista) y de manera similar la clase B tiene un método que toma una instancia de la clase A y devuelve una instancia de clase B (convirtiendo la lista en una tupla).

Author: Sheena, 2011-09-07

3 answers

Solo importa el módulo, no importa 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.

 67
Author: rumpel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-07 15:55:37

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 las opciones 1st y 4th realmente funcionan cuando se tienen dependencias circulares (el resto plantea ImportError o AttributeError). En general, no debería usar la sintaxis 4th, ya que solo funciona en python2 y corre el riesgo de chocar con otros módulos de 3rd party. Así que en realidad, sólo la primera la 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 la maquinaria de importación ha sido reescrita y todo de estas declaraciones de importación (con la excepción de 4) funcionará, incluso con dependencias circulares.

Use Importaciones absolutas

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

{[22] {} En[9]}
import package.b
{[22] {} En[10]}
import package.a

Aplazar la importación hasta más tarde

He visto que este método se usa en muchos paquetes, pero todavía me parece hackeado, y no me gusta que no pueda mirar 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.

{[22] {} En[9]}
def func():
    from package import b
{[22] {} En[10]}
def func():
    from package import a

Poner todas las importaciones en una central módulo

Esto también funciona, pero tiene el mismo problema que el primer método, donde todas las llamadas a paquetes y submódulos obtienen super long. También tiene dos fallas principales: obliga a a importar todos los submódulos, incluso si solo está usando uno o dos, y aún no puede mirar ninguno de los submódulos y ver rápidamente sus dependencias en la parte superior, tiene que ir tamizando funciones.

In __init__.py

from . import a
from . import b
{[22] {} En[9]}
import package

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

En b.py

import package

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

Así que esas son tus opciones (y todas apestan un poco IMO). Francamente, esto parece ser un error evidente en la maquinaria de importación de Python, pero eso es solo mi opinión.

 114
Author: Brendan Abel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-17 01:08:49

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 las importaciones absolutas puras
  • Desventaja: un poco más de sobrecarga debido a la llamada a la 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__)
 4
Author: Christian Haintz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-08 13:30:40