Python: циклический импорт необходим для проверки типов - PullRequest
8 голосов
/ 17 марта 2010

Прежде всего: я знаю, что уже есть много вопросов и ответов на тему кругового импорта.

Ответ более или менее: «Правильно спроектируйте структуру вашего модуля / класса, и вам не понадобится циклический импорт». Это правда. Я очень старался сделать правильный дизайн для моего текущего проекта, я думаю, что мне это удалось.

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

Вот так:

foo.py:

from bar import Bar

class Foo(object):

    def __init__(self):
        self.__bar = Bar(self)

bar.py:

from foo import Foo

class Bar(object):

    def __init__(self, arg_instance_of_foo):
        if not isinstance(arg_instance_of_foo, Foo):
            raise TypeError()

Решение 1: Если я изменил его, чтобы проверить тип путем сравнения строк, он будет работать. Но мне не очень нравится это решение (сравнение строк довольно дорого для простой проверки типов и может вызвать проблемы при рефакторинге).

bar_modified.py:

from foo import Foo

class Bar(object):

    def __init__(self, arg_instance_of_foo):
        if not arg_instance_of_foo.__class__.__name__ == "Foo":
            raise TypeError()

Решение 2: Я также мог бы упаковать два класса в один модуль. Но в моем проекте много разных классов, таких как пример "Bar", и я хочу разделить их на разные файлы модулей.

После моих собственных 2-х решений для меня нет выбора: есть ли у кого-нибудь более подходящее решение для этой проблемы?

Ответы [ 3 ]

5 голосов
/ 17 марта 2010

Вы можете программировать для interface (ABC - абстрактный базовый класс в Python), а не для конкретного типа Bar. Это классический способ разрешения взаимозависимостей пакетов / модулей во многих языках. Концептуально это также должно привести к лучшему проектированию объектной модели.

В вашем случае вы определяете интерфейс IBar в каком-либо другом модуле (или даже в модуле, который содержит класс Foo - зависит от использования этого abc) Ваш код тогда выглядит так:

foo.py:

from bar import Bar, IFoo

class Foo(IFoo):
    def __init__(self):
        self.__bar = Bar(self)

# todo: remove this, just sample code
f = Foo()
b = Bar(f)
print f
print b
x = Bar('do not fail me please') # this fails

bar.py:

from abc import ABCMeta
class IFoo:
    __metaclass__ = ABCMeta

class Bar(object):
    def __init__(self, arg_instance_of_foo):
        if not isinstance(arg_instance_of_foo, IFoo):
            raise TypeError()
5 голосов
/ 17 марта 2010

Лучшее решение - не проверять типы.

Другое решение состоит в том, чтобы не создавать экземпляр и не ссылаться на Foo или Bar до тех пор, пока не будут загружены оба класса. Если первый модуль загружается первым, не создавайте Bar и не обращайтесь к Bar до тех пор, пока не будет выполнен оператор class Foo. Аналогично, если второй модуль загружается первым, не создавайте Foo или ссылку Foo до тех пор, пока не будет выполнен оператор class Bar.

Это в основном источник ImportError, которого можно было бы избежать, если бы вместо этого вы использовали "import foo" и "import bar" и использовали foo.Foo там, где вы теперь используете Foo, и bar.Bar где теперь вы используете Bar. При этом вы больше не обращаетесь к одному из них до тех пор, пока не будет создан Foo или Bar, что, мы надеемся, не произойдет, пока не будут созданы оба (или вы получите AttributeError).

2 голосов
/ 17 марта 2010

Вы можете просто отложить импорт в bar.py следующим образом:

class Bar(object):

    def __init__(self, arg_instance_of_foo):
        from foo import Foo
        if not isinstance(arg_instance_of_foo, Foo):
            raise TypeError()
...