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

11

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

答案大体上是:“正确设计您的模块/类结构,您就不需要循环导入”。这是正确的。我为我的当前项目努力制定了一个适当的设计,在我看来我成功了。

但我的具体问题是:我需要在一个已经被包含类检查的模块中进行类型检查。但这会抛出一个导入错误。

像这样:

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”示例这样的不同类,我想将它们分别放在不同的模块文件中。

在我的两个解决方案都不可行之后:是否有人有更好的解决方案来解决这个问题?


请查看 https://dev59.com/JlkS5IYBdhLWcg3wgXLf 获取正确答案。 - Dominique PERETTI
4个回答

7
你可以使用编程接口(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()

1
:) 很公平。在Java和其他强类型语言中,这些问题经常出现,并有一些“标准”解决方案。在Python中,鸭子类型是“方式”,因此这“不是问题”。尽管如此,我认为一旦提出问题,就需要确保类型。 - van

6
最好的解决方案是不进行类型检查。
另一种解决方案是在两个类都加载后再创建实例并且不要引用FooBar。如果第一个模块先加载,不要在执行class Foo语句之前创建Bar或引用Bar。同样地,如果第二个模块先加载,不要在执行class Bar语句之前创建Foo或引用Foo
这基本上是ImportError的源头,如果您使用"import foo"和"import bar",并在现在使用Foo的地方使用foo.Foo,在现在使用Bar的地方使用bar.Bar,就可以避免出现ImportError。通过这样做,直到创建FooBar之后才会引用它们,希望这不会在两者都创建之前发生(否则您将获得AttributeError)。

2
您可以像这样将bar.py中的导入延迟:

您可以像这样将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()

1
所有的导入应该放在顶部,除非有一个非常好的理由不这样做。这不是那个理由。 - Devin Jeanpierre
@Devin:你的回答更好,但这是处理循环导入的标准方式,所以这个答案仍然有价值。 - FogleBird
@Devin Jeanpierre:我还能做什么呢? - Philip Daubmeier
@FogleBird:这不是我听说过的标准(或成语等)。 - Devin Jeanpierre

1

可能是重复问题:Python中的类型提示无循环导入

你应该使用前向引用(PEP 484-类型提示):

当类型提示包含尚未定义的名称时,可以将该定义表示为字符串文字以在稍后解决。

所以不要写成:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

做:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

类型提示 != 类型检查 - Gesuchter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接