检查 class 的子/父关系,同时防止循环导入/循环依赖

Checking the child / parent relationship of a class while preventing circular import / circular dependency

我有这些模块:

# module 1

from abc import ABC
from module3 import AnotherClass


class Parent(ABC):

    @classmethod
    def func1(cls):

        other_param = "foo"
        AnotherClass.some_func(cls, other_param)
# module 2

from module1 import Parent
from module3 import AnotherClass


class Child1(Parent):

    pass


class Child2(Parent):

    pass
# module 3

from module1 import Parent # only for checking if the object is a subclass of Parent


class AnotherClass(object):

    @classmethod
    def some_func(cls, child_cls, other_param):

        assert issubclass(child_cls, Parent)
        # do other stuff

现在如果我这样做:

c1 = Child1()

c1.func1()

我正确地从循环导入中得到一个 ImportError 抱怨如下:

ImportError: cannot import name 'Parent' from partially initialized module 'module1' (most likely due to a circular import)

实际上 AnotherClass 不依赖于 Parent 或任何子 classes,我导入 Parent 只是为了检查传递的 class 是 Parent 的子 class,在这一行中:

assert issubclass(child_cls, Parent)

现在,我可以像这样延迟导入 Parent

# module 3


class AnotherClass(object):

    @classmethod
    def some_func(cls, child_cls, other_param):

        from module1 import Parent # only for type checking
        assert issubclass(child_cls, Parent)
        # do other stuff

它就像一个魅力。但出于某种原因(我不知道)我觉得这样做很不舒服。

现在我的问题是,我可以安心地推迟这样的进口吗,还是它会在以后以某种方式伤害我?

如果是,您建议我如何在 AnotherClass.some_func() 进行此父/子关系检查?

P.S. 在有人提出之前,我知道这个循环依赖的话题已经被询问和回答了很多次(比如this one),但我想问的是这个具体的解决方案是否可行并且在很长一段时间内没有问题运行。

首先,你并不是真的在那里做 "type checking",assert 是完全不同的事情,因为它们实际上是在 运行 时间处理的。

我认为您最好使用实际的类型提示,通过使用它们,有一种非常简单且惯用的方法可以避免循环导入问题:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from module1 import Parent


class AnotherClass:  # No need to inherit from `object` in Python 3.

    @classmethod
    def some_func(cls, child_cls: "Parent", other_param):
        ...

您当前的解决方案本身没有任何问题。在这种情况下,你也可以像这样解决它,这样更干净一些:

import module1


class AnotherClass(object):

    @classmethod
    def some_func(cls, child_cls, other_param):
        assert issubclass(child_cls, module1.Parent)