Подтвердить что ты не робот

Как избежать циклического импорта в Python?

Я знаю, что проблема циклического импорта в python возникла много раз раньше, и я прочитал эти обсуждения. Комментарий, который неоднократно повторяется в этих обсуждениях, заключается в том, что циклический импорт является признаком плохой конструкции, и код должен быть реорганизован, чтобы избежать циклического импорта.

Может ли кто-нибудь сказать мне, как избежать циклического импорта в этой ситуации?: У меня есть два класса, и я хочу, чтобы каждый класс имел конструктор (метод), который принимает экземпляр другого класса и возвращает экземпляр класса.

Более конкретно, один класс является изменяемым, а один неизменным. Требуется неизменный класс для хэширования, сравнения и т.д. Переменный класс необходим, чтобы что-то делать. Это похоже на наборы и фризонсет, или на списки и кортежи.

Я мог бы поместить оба определения классов в один и тот же модуль. Есть ли другие предложения?

Примером игрушки может быть класс A, который имеет атрибут, который является списком и классом B, который имеет атрибут, который является кортежем. Затем класс A имеет метод, который принимает экземпляр класса B и возвращает экземпляр класса A (путем преобразования кортежа в список), и аналогично класс B имеет метод, который принимает экземпляр класса A и возвращает экземпляр класса B (путем преобразования списка в кортеж).

4b9b3361

Ответ 1

Только импортируйте модуль, не импортируйте его из модуля:

Рассмотрим a.py:

import b

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

и b.py:

import a

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

Это прекрасно работает.

Ответ 2

Рассмотрим следующий пример пакета Python, в котором a.py и b.py зависят друг от друга:

/package
    __init__.py
    a.py
    b.py

Типы проблем кругового импорта

Круговые зависимости импорта обычно делятся на две категории в зависимости о том, что вы пытаетесь импортировать и где вы используете его внутри каждого модуль. (И используете ли вы Python 2 или 3).

1. Ошибки при импорте модулей с циклическим импортом

В некоторых случаях просто импортируйте модуль с циклической зависимостью импорта может привести к ошибкам, даже если вы ничего не ссылаетесь на импортированный модуль.

Есть несколько стандартных способов импортировать модуль в Python

import package.a           # (1) Absolute import
import package.a as a_mod  # (2) Absolute import bound to different name
from package import a      # (3) Alternate absolute import
import a                   # (4) Implicit relative import (deprecated, python 2 only)
from . import a            # (5) Explicit relative import

К сожалению, только 1-й и 4-й варианты действительно работают, когда вы имеют круговые зависимости (остальные все поднимают ImportError или AttributeError). В общем, вы не должны использовать 4-й синтаксис, так как он работает только в Python2 и рискует конфликт с другими сторонними модулями. Так реально только первый синтаксис гарантированно работает.

ОБНОВЛЕНИЕ: проблемы ImportError и AttributeError возникают только в Python 2. В Python 3 была переписана импортная техника и все из этих операторов импорта (за исключением 4) будет работать, даже с круговые зависимости. Решения в этом разделе предназначены для людей, использующих Python 2.

Абсолютный импорт

Просто используйте первый синтаксис импорта выше. Недостатком этого метода является что имена импорта могут быть очень длинными для больших пакетов.

В a.py

import package.b

В b.py

import package.a

Отложить импорт на потом

Я видел этот метод, используемый во многих пакетах, но он все еще чувствует мне повезло, и мне не нравится, что я не могу посмотреть на верхнюю часть модуля и увидеть все его зависимости, я должен искать все функции также.

В a.py

def func():
    from package import b

В b.py

def func():
    from package import a

Поместите весь импорт в центральный модуль

Это также работает, но имеет ту же проблему, что и первый метод, где все пакетные и субмодульные звонки становятся очень длинными. У этого также есть два основные недостатки - это заставляет импортировать все подмодули, даже если вы используете только один или два, и вы все еще не можете смотреть на любой из субмодули и быстро увидеть их зависимости в верхней части, вы должны просеять функции.

В __init__.py

from . import a
from . import b

В a.py

import package

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

В b.py

import package

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

2. Ошибки при использовании импортированных объектов с круговыми зависимостями

Теперь, хотя вы можете импортировать модуль с циклическим импортом зависимость, вы не сможете импортировать объекты, определенные в модуле или на самом деле сможет ссылаться на этот импортированный модуль в любом месте в верхнем уровне модуля, куда вы его импортируете. Ты можешь, однако используйте импортированный модуль внутри функций и блоков кода, которые не запустить при импорте.

Например, это будет работать:

пакет/a.py

import package.b

def func_a():
    return "a"

пакет/b.py

import package.a

def func_b():
    # Notice how package.a is only referenced *inside* a function
    # and not the top level of the module.
    return func_a() + "b"

Но это не сработает

пакет/a.py

import package.b

class A(object):
    pass

пакет/b.py

import package.a

# package.a is referenced at the top level of the module
class B(package.a.A):
    pass

Вы получите исключение

AttributeError: модуль 'package' не имеет атрибута 'a'

Как правило, в большинстве действительных случаев циклических зависимостей возможно реорганизовать или реорганизовать код для предотвращения этих ошибок и перемещения ссылки на модули внутри блока кода.

Ответ 3

Мы выполняем комбинацию абсолютного импорта и функций для лучшего чтения и более коротких строк доступа.

  • Преимущество: более короткие строки доступа по сравнению с абсолютным абсолютным импортом.
  • Недостаток: немного больше накладных расходов из-за дополнительного вызова функции

главный/подчиненный/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__)

главный/подчиненный/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__)