Рекурсивные операторы импорта - PullRequest
4 голосов
/ 17 апреля 2019

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

Организация файлов в пакете temp выглядит следующим образом:

|-- __init__.py
|-- abstract.py
|-- a.py
|-- b.py

Файл __init__.py содержит

from .a import A
from .b import B

Файл abstract.py содержит

from abc import ABC, abstractmethod

class Abstract(ABC):

    @abstractmethod
    def __init__(self):
        pass

Файл a.py содержит

from .abstract import Abstract
from .b import B

class A(Abstract):

    def __init__(self, string):
        super().__init__()
        self.string = string

    def as_b(self):
        return B(int(self.string))

Файл b.py содержит

from .abstract import Abstract
from .a import A

class B(Abstract):

    def __init__(self, integer):
        super().__init__()
        self.integer = integer

    def as_a(self):
        return A(str(self.integer))

I тогдасоздал файл foo.py для проверки пакета temp, который содержит

from temp import A, B

an_a = A("2")
a_b = B(4)

an_a_as_b = A.as_b()
a_b_as_a = B.as_a()

Во время выполнения я получаю ошибку ImportError: cannot import name 'A'.Насколько я понимаю, это связано с рекурсивным оператором import (класс A, импортирующий класс B и наоборот).

Каков наилучший питонный способ реализации классов A и B в temp пакет?

1 Ответ

2 голосов
/ 18 апреля 2019

Помимо слияния файлов, существует несколько опций, которые, как вы сказали, не одобряет ваша конвенция на рабочем месте. (Варианты, приведенные ниже, адаптированы для Python 3 относительно ответа, связанного в этом комментарии от Sanyash . Я подумал, что имеет смысл включить сюда ответ, поскольку этот вопрос не касается непосредственно относительного импорта.)

Переместить циклический импорт в конец своих файлов

Файл a.py становится:

from .abstract import Abstract

class A(Abstract):
    # ...

from .b import B

и b.py изменяются одинаково. Это простое изменение, но имеет тот недостаток, что ваш импорт разбросан. Кто-то может «очистить» ваш код, переместив импорт в начало, и столкнуться с той же путаницей, что и у вас. (Вы можете оставить комментарий, но люди часто пропускают комментарии.)

Импорт модулей вместо классов

Вы также можете сделать так, чтобы каждый модуль импортировал другой модуль, а не импортировал из него. a.py становится:

from .abstract import Abstract
from . import b

class A(Abstract):
    # ...
    def as_b(self):
        return b.B(int(self.string)) # note the change to use b.B

и, конечно, b.py изменяется аналогично. Это кажется мне самым чистым решением - я настоятельно предпочитаю сохранять импорт вместе, когда это возможно. (Я не могу думать о каких-либо недостатках этого подхода, но если кто-то знает о них, пожалуйста, оставьте комментарий, и я обновлю его.)

Переместить импорт туда, где он используется

Если вы ссылаетесь только на B из a.py в одном месте, как это, вы можете переместить импорт в функцию, где это необходимо:

from .abstract import Abstract

class A(Abstract):
    # ...

    def as_b(self):
        from .b import B
        return B(int(self.string))

и то же самое для b.py. Это имеет преимущество перед первым вариантом: легче понять, почему импорт не на вершине, и я чувствую, что это менее вероятно приведет к путанице или ошибкам в будущем. Однако он может маскировать ошибки импорта, поэтому я не думаю, что он идеален. Это также немного замедлит первый вызов as_a и as_b, поскольку импорт будет происходить при первом вызове каждой функции и займет ненулевое время.

...