如何找到一个类的所有子类?

368 阅读2分钟

问题的提出

给定一个类的名称(字符串)。如何找到该类的所有子类?

例子。下面是一个有子类层次结构的例子。

class Parent:
    pass

class Daughter(Parent):
    pass

class Son(Parent):
    pass

class Grandson(Son):
    pass


预期的输出。接下来,让我们通过两个例子快速确定你想要完成的任务。

Given: Son Result: Grandson

Given: Parent Result: Daughter, Son

对于最基本的情况,我们不假设有递归的解决要求,所以我们不考虑Grandson 也是Parent 的子类。

如果我们有了类对象,而不仅仅是类名,那么就可以解决了

假设(现在)我们有类对象。在本文的下一节中,我们将看到一个略微复杂的情况,没有这个假设。

在这种情况下,我们可以简单地使用my_class.__subclasses__() 魔法方法获得该类的所有子类。

class Parent:
    pass

class Daughter(Parent):
    pass

class Son(Parent):
    pass

class Grandson(Son):
    pass


print(Parent.__subclasses__())
# [<class '__main__.Daughter'>, <class '__main__.Son'>]

如果你只需要一个所有子类的名字的列表,而不需要整个杂乱无章的输出,你可以使用一个列表理解语句,就像这样。

names = [cls.__name__ for cls in Parent.__subclasses__()]
print(names)
# ['Daughter', 'Son']

对所有子类的递归

你可以使用递归的方法获得所有直接和间接的子类(如果使用分层继承)。

  • 创建一个函数subclass_recursive() ,该函数需要一个参数:基类,应该从该基类中找到子类。
  • 使用__subclasses__() 魔法方法找到所有的直接子类。
  • 通过递归调用该函数找到所有的间接子类。
  • 将直接和间接子类的串联结果返回给调用者。

如果你需要复习一下递归,请看我 在这里深度教程

让我们深入了解一下代码吧!

class Parent:
    pass

class Daughter(Parent):
    pass

class Son(Parent):
    pass

class Grandson(Son):
    pass

class GrandGrandSon(Son):
    pass


def subclasses_recursive(cls):
    direct = cls.__subclasses__()
    indirect = []
    for subclass in direct:
        indirect.extend(subclasses_recursive(subclass))
    return direct + indirect

print(subclasses_recursive(Parent))

输出是所有子类的列表

[<class '__main__.Daughter'>, <class '__main__.Son'>, 
 <class '__main__.Grandson'>, <class '__main__.GrandGrandSon'>]

同样,如果你只需要有字符串形式的类名,可以使用列表理解法,在子类上使用 [__name__](https://blog.finxter.com/what-does-if-__name__-__main__-do-in-python/)特殊属性进行列表理解。

names = [cls.__name__ for cls in subclasses_recursive(Parent)]
print(names)
# ['Daughter', 'Son', 'Grandson', 'GrandGrandSon']

如果我们只拥有类的字符串名称

你可以使用 globals()内置函数来找出给定名称的类对象。

# Get class given name of class:
base_name = 'Parent'
base_class = globals()[base_name]

接下来请随意观看我关于该函数的背景视频。

如果这不起作用,你可能想检查一下 [locals()](https://blog.finxter.com/python-locals/)函数。

# Get class given name of class:
base_name = 'Parent'
base_class = locals()[base_name]

最后,这里概述了另一种获得类的完整路径的方法。

import importlib
module_name, _, base_name = name.rpartition('.')
module = importlib.import_module(module_name)
base_class = getattr(module, base_name)

把它放在一起

class Parent:
    pass

class Daughter(Parent):
    pass

class Son(Parent):
    pass

class Grandson(Son):
    pass

class GrandGrandSon(Son):
    pass

# Get class given name of class:
base_name = 'Parent'
base_class = globals()[base_name]

# Get list of direct subclasses:
print(base_class.__subclasses__())
# [<class '__main__.Daughter'>, <class '__main__.Son'>]

# Get list of direct subclass names:
names = [cls.__name__ for cls in base_class.__subclasses__()]
print(names)
# ['Daughter', 'Son']

# Get list of direct and indirect subclasses:
def subclasses_recursive(cls):
    direct = cls.__subclasses__()
    indirect = []
    for subclass in direct:
        indirect.extend(subclasses_recursive(subclass))
    return direct + indirect

# Get list of direct and indirect subclasse names:
names = [cls.__name__ for cls in subclasses_recursive(base_class)]
print(names)
# ['Daughter', 'Son', 'Grandson', 'GrandGrandSon']