Python:类型检查需要循环导入

11 投票
4 回答
5499 浏览
提问于 2025-04-15 20:31

首先,我知道关于循环导入的问题已经有很多问答了。

简单来说,答案就是:“好好设计你的模块或类结构,就不需要循环导入。”这话没错。我在当前项目中努力设计,觉得自己做得还不错。

但我遇到的具体问题是:我需要在一个模块中进行类型检查,而这个模块已经被包含类的模块导入了。这就导致了导入错误。

就像这样:

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”这个例子,我想把它们分开到不同的模块文件中。

既然我自己的两个解决方案都不适合我:有没有人能提供一个更好的解决方案呢?

4 个回答

2

你可以在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()
7

你可以通过编程来使用 interface(在 Python 中叫做 ABC,即 抽象基类),而不是直接使用具体的类型 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()
6

最好的办法就是不检查类型。

另一种解决方案是,在两个类都加载之前,不去创建或引用 FooBar。如果第一个模块先加载,就等到 class Foo 这行代码执行完后,再去创建或引用 Bar。同样的,如果第二个模块先加载,就等到 class Bar 这行代码执行完后,再去创建或引用 Foo

这其实就是 ImportError 的根源。如果你改成先 "import foo" 和 "import bar",然后在需要用到 Foo 的地方用 foo.Foo,在需要用到 Bar 的地方用 bar.Bar,就能避免这个问题。这样做的话,直到创建 FooBar 之前,你都不会去引用它们,这样就能确保在两个都创建完之后再使用它们(否则你会遇到 AttributeError 的错误)。

撰写回答