如何根据给定名称查找一个类的所有子类?

发布于 2024-09-26 03:03:27 字数 37 浏览 1 评论 0原文

我需要一种获取从 Python 基类继承的所有类的工作方法。

I need a working approach of getting all classes that are inherited from a base class in Python.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

失去的东西太少 2024-10-03 03:03:27

新式类(即从 object 派生的子类,这是 Python 3 中的默认设置)具有返回子类的 __subclasses__ 方法:

class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass

以下是子类的名称:

print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']

这里是子类本身:

print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]

确认子类确实将 Foo 列为其基类:

for cls in Foo.__subclasses__():
    print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>

请注意,如果您想要子子类,则必须递归:

def all_subclasses(cls):
    return set(cls.__subclasses__()).union(
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])

print(all_subclasses(Foo))
# {<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>}

请注意,如果子类的类定义尚未定义尚未执行 - 例如,如果子类的模块尚未导入 - 那么该子类尚不存在,并且 __subclasses__ 不会找到它。


你提到“给它的名字”。由于 Python 类是一等对象,因此您不需要使用带有类名的字符串来代替类或类似的内容。您可以直接使用该类,而且您可能应该这样做。

如果您确实有一个表示类名称的字符串,并且想要查找该类的子类,则有两个步骤:根据名称查找该类,然后使用上面的 __subclasses__ 查找子类。

如何从名称找到类取决于您期望在哪里找到它。如果您希望在与尝试定位该类的代码相同的模块中找到它,那么

cls = globals()[name]

就可以完成这项工作,或者在不太可能的情况下,您希望在局部变量中找到它,

cls = locals()[name]

如果该类可能位于任何模块,那么您的名称字符串应包含完全限定的名称 - 类似 'pkg.module.Foo' 而不仅仅是 'Foo'。使用importlib加载类的模块,然后检索相应的属性:

import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)

无论您如何找到该类,cls.__subclasses__()都会返回其子类的列表。

New-style classes (i.e. subclassed from object, which is the default in Python 3) have a __subclasses__ method which returns the subclasses:

class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass

Here are the names of the subclasses:

print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']

Here are the subclasses themselves:

print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]

Confirmation that the subclasses do indeed list Foo as their base:

for cls in Foo.__subclasses__():
    print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>

Note if you want subsubclasses, you'll have to recurse:

def all_subclasses(cls):
    return set(cls.__subclasses__()).union(
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])

print(all_subclasses(Foo))
# {<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>}

Note that if the class definition of a subclass hasn't been executed yet - for example, if the subclass's module hasn't been imported yet - then that subclass doesn't exist yet, and __subclasses__ won't find it.


You mentioned "given its name". Since Python classes are first-class objects, you don't need to use a string with the class's name in place of the class or anything like that. You can just use the class directly, and you probably should.

If you do have a string representing the name of a class and you want to find that class's subclasses, then there are two steps: find the class given its name, and then find the subclasses with __subclasses__ as above.

How to find the class from the name depends on where you're expecting to find it. If you're expecting to find it in the same module as the code that's trying to locate the class, then

cls = globals()[name]

would do the job, or in the unlikely case that you're expecting to find it in locals,

cls = locals()[name]

If the class could be in any module, then your name string should contain the fully-qualified name - something like 'pkg.module.Foo' instead of just 'Foo'. Use importlib to load the class's module, then retrieve the corresponding attribute:

import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)

However you find the class, cls.__subclasses__() would then return a list of its subclasses.

稳稳的幸福 2024-10-03 03:03:27

如果您只想要直接子类,那么 .__subclasses__() 工作正常。如果您想要所有子类、子类的子类等等,您将需要一个函数来为您执行此操作。

这是一个简单、可读的函数,它递归地查找给定类的所有子类:

def get_all_subclasses(cls):
    all_subclasses = []

    for subclass in cls.__subclasses__():
        all_subclasses.append(subclass)
        all_subclasses.extend(get_all_subclasses(subclass))

    return all_subclasses

If you just want direct subclasses then .__subclasses__() works fine. If you want all subclasses, subclasses of subclasses, and so on, you'll need a function to do that for you.

Here's a simple, readable function that recursively finds all subclasses of a given class:

def get_all_subclasses(cls):
    all_subclasses = []

    for subclass in cls.__subclasses__():
        all_subclasses.append(subclass)
        all_subclasses.extend(get_all_subclasses(subclass))

    return all_subclasses
天暗了我发光 2024-10-03 03:03:27

一般形式的最简单的解决方案:

def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from get_subclasses(subclass)
        yield subclass

以及一个类方法,如果您有一个继承自的类:

@classmethod
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from subclass.get_subclasses()
        yield subclass

The simplest solution in general form:

def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from get_subclasses(subclass)
        yield subclass

And a classmethod in case you have a single class where you inherit from:

@classmethod
def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield from subclass.get_subclasses()
        yield subclass
心房敞 2024-10-03 03:03:27

Python 3.6 - __init_subclass__

正如其他答案提到的,您可以检查 __subclasses__ 属性来获取子类列表,因为 python 3.6 您可以修改此属性通过覆盖 创建__init_subclass__ 方法。

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

这样,如果您知道自己在做什么,则可以覆盖 __subclasses__ 的行为并从此列表中省略/添加子类。

Python 3.6 - __init_subclass__

As other answer mentioned you can check the __subclasses__ attribute to get the list of subclasses, since python 3.6 you can modify this attribute creation by overriding the __init_subclass__ method.

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

This way, if you know what you're doing, you can override the behavior of of __subclasses__ and omit/add subclasses from this list.

尛丟丟 2024-10-03 03:03:27

注意:我看到有人(不是 @unutbu)更改了引用的答案,使其不再使用 vars()['Foo'] - 因此我帖子的主要观点不再适用.

FWIW,这就是我关于 @unutbu 的答案 仅使用本地定义的类的意思 - 并且使用 < code>eval() 而不是 vars() 将使其适用于任何可访问的类,而不仅仅是当前范围中定义的类。

对于那些不喜欢使用 eval() 的人,还提供了一种避免使用它的方法。

首先,这是一个具体示例,演示了使用 vars() 的潜在问题:

class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass

# unutbu's approach
def all_subclasses(cls):
    return cls.__subclasses__() + [g for s in cls.__subclasses__()
                                       for g in all_subclasses(s)]

print(all_subclasses(vars()['Foo']))  # Fine because  Foo is in scope
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

def func():  # won't work because Foo class is not locally defined
    print(all_subclasses(vars()['Foo']))

try:
    func()  # not OK because Foo is not local to func()
except Exception as e:
    print('calling func() raised exception: {!r}'.format(e))
    # -> calling func() raised exception: KeyError('Foo',)

print(all_subclasses(eval('Foo')))  # OK
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

# using eval('xxx') instead of vars()['xxx']
def func2():
    print(all_subclasses(eval('Foo')))

func2()  # Works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

可以通过将 eval('ClassName') 向下移动到定义的函数中来改进,这使得使用它更容易,而不损失通过使用 eval() 获得的额外通用性,它与 vars() 不同,它不是上下文相关的:

# easier to use version
def all_subclasses2(classname):
    direct_subclasses = eval(classname).__subclasses__()
    return direct_subclasses + [g for s in direct_subclasses
                                    for g in all_subclasses2(s.__name__)]

# pass 'xxx' instead of eval('xxx')
def func_ez():
    print(all_subclasses2('Foo'))  # simpler

func_ez()
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

最后,它是可能的,甚至可能很重要在某些情况下,出于安全原因避免使用 eval() ,因此这里有一个没有它的版本:

def get_all_subclasses(cls):
    """ Generator of all a class's subclasses. """
    try:
        for subclass in cls.__subclasses__():
            yield subclass
            for subclass in get_all_subclasses(subclass):
                yield subclass
    except TypeError:
        return

def all_subclasses3(classname):
    for cls in get_all_subclasses(object):  # object is base of all new-style classes.
        if cls.__name__.split('.')[-1] == classname:
            break
    else:
        raise ValueError('class %s not found' % classname)
    direct_subclasses = cls.__subclasses__()
    return direct_subclasses + [g for s in direct_subclasses
                                    for g in all_subclasses3(s.__name__)]

# no eval('xxx')
def func3():
    print(all_subclasses3('Foo'))

func3()  # Also works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

Note: I see that someone (not @unutbu) changed the referenced answer so that it no longer uses vars()['Foo'] — so the primary point of my post no longer applies.

FWIW, here's what I meant about @unutbu's answer only working with locally defined classes — and that using eval() instead of vars() would make it work with any accessible class, not only those defined in the current scope.

For those who dislike using eval(), a way is also shown to avoid it.

First here's a concrete example demonstrating the potential problem with using vars():

class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass

# unutbu's approach
def all_subclasses(cls):
    return cls.__subclasses__() + [g for s in cls.__subclasses__()
                                       for g in all_subclasses(s)]

print(all_subclasses(vars()['Foo']))  # Fine because  Foo is in scope
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

def func():  # won't work because Foo class is not locally defined
    print(all_subclasses(vars()['Foo']))

try:
    func()  # not OK because Foo is not local to func()
except Exception as e:
    print('calling func() raised exception: {!r}'.format(e))
    # -> calling func() raised exception: KeyError('Foo',)

print(all_subclasses(eval('Foo')))  # OK
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

# using eval('xxx') instead of vars()['xxx']
def func2():
    print(all_subclasses(eval('Foo')))

func2()  # Works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

This could be improved by moving the eval('ClassName') down into the function defined, which makes using it easier without loss of the additional generality gained by using eval() which unlike vars() is not context-sensitive:

# easier to use version
def all_subclasses2(classname):
    direct_subclasses = eval(classname).__subclasses__()
    return direct_subclasses + [g for s in direct_subclasses
                                    for g in all_subclasses2(s.__name__)]

# pass 'xxx' instead of eval('xxx')
def func_ez():
    print(all_subclasses2('Foo'))  # simpler

func_ez()
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]

Lastly, it's possible, and perhaps even important in some cases, to avoid using eval() for security reasons, so here's a version without it:

def get_all_subclasses(cls):
    """ Generator of all a class's subclasses. """
    try:
        for subclass in cls.__subclasses__():
            yield subclass
            for subclass in get_all_subclasses(subclass):
                yield subclass
    except TypeError:
        return

def all_subclasses3(classname):
    for cls in get_all_subclasses(object):  # object is base of all new-style classes.
        if cls.__name__.split('.')[-1] == classname:
            break
    else:
        raise ValueError('class %s not found' % classname)
    direct_subclasses = cls.__subclasses__()
    return direct_subclasses + [g for s in direct_subclasses
                                    for g in all_subclasses3(s.__name__)]

# no eval('xxx')
def func3():
    print(all_subclasses3('Foo'))

func3()  # Also works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
清晰传感 2024-10-03 03:03:27

下面是一个简单但高效的代码版本:

def get_all_subclasses(cls):
    subclass_list = []

    def recurse(klass):
        for subclass in klass.__subclasses__():
            subclass_list.append(subclass)
            recurse(subclass)

    recurse(cls)

    return set(subclass_list)

它的时间复杂度为 O(n),其中 n 是如果没有多重继承的话所有子类的数量。
它比使用生成器递归创建列表或生成类的函数更有效,当类层次结构是平衡树时,其复杂度可能为 (1) O(nlogn) 或 (2) O (n^2) 当类层次结构是偏向树时。

Here is a simple but efficient version of code:

def get_all_subclasses(cls):
    subclass_list = []

    def recurse(klass):
        for subclass in klass.__subclasses__():
            subclass_list.append(subclass)
            recurse(subclass)

    recurse(cls)

    return set(subclass_list)

Its time complexity is O(n) where n is the number of all subclasses if there's no multiple inheritance.
It's more efficient than the functions that recursively create lists or yield classes with generators, whose complexity could be (1) O(nlogn) when the class hierarchy is a balanced tree or (2) O(n^2) when the class hierarchy is a biased tree.

怀中猫帐中妖 2024-10-03 03:03:27

用于获取所有子类列表的更短版本:

from itertools import chain

def subclasses(cls):
    return list(
        chain.from_iterable(
            [list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
        )
    )

A much shorter version for getting a list of all subclasses:

from itertools import chain

def subclasses(cls):
    return list(
        chain.from_iterable(
            [list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
        )
    )
生生漫 2024-10-03 03:03:27

这是一个没有递归的版本:

def get_subclasses_gen(cls):

    def _subclasses(classes, seen):
        while True:
            subclasses = sum((x.__subclasses__() for x in classes), [])
            yield from classes
            yield from seen
            found = []
            if not subclasses:
                return

            classes = subclasses
            seen = found

    return _subclasses([cls], [])

这与其他实现的不同之处在于它返回原始类。
这是因为它使代码更简单,并且:

class Ham(object):
    pass

assert(issubclass(Ham, Ham)) # True

如果 get_subclasses_gen 看起来有点奇怪,那是因为它是通过将尾递归实现转换为循环生成器创建的:

def get_subclasses(cls):

    def _subclasses(classes, seen):
        subclasses = sum(*(frozenset(x.__subclasses__()) for x in classes))
        found = classes + seen
        if not subclasses:
            return found

        return _subclasses(subclasses, found)

    return _subclasses([cls], [])

Here's a version without recursion:

def get_subclasses_gen(cls):

    def _subclasses(classes, seen):
        while True:
            subclasses = sum((x.__subclasses__() for x in classes), [])
            yield from classes
            yield from seen
            found = []
            if not subclasses:
                return

            classes = subclasses
            seen = found

    return _subclasses([cls], [])

This differs from other implementations in that it returns the original class.
This is because it makes the code simpler and:

class Ham(object):
    pass

assert(issubclass(Ham, Ham)) # True

If get_subclasses_gen looks a bit weird that's because it was created by converting a tail-recursive implementation into a looping generator:

def get_subclasses(cls):

    def _subclasses(classes, seen):
        subclasses = sum(*(frozenset(x.__subclasses__()) for x in classes))
        found = classes + seen
        if not subclasses:
            return found

        return _subclasses(subclasses, found)

    return _subclasses([cls], [])
千笙结 2024-10-03 03:03:27

如何找到给定名称的类的所有子类?

如果可以访问对象本身,我们当然可以轻松地做到这一点,是的。

简单地给出它的名称是一个糟糕的主意,因为可以有多个同名的类,甚至是在同一个模块中定义的。

我为另一个答案创建了一个实现,因为它回答了这个问题并且比这里的其他解决方案更优雅,这是:

def get_subclasses(cls):
    """returns all subclasses of argument, cls"""
    if issubclass(cls, type):
        subclasses = cls.__subclasses__(cls)
    else:
        subclasses = cls.__subclasses__()
    for subclass in subclasses:
        subclasses.extend(get_subclasses(subclass))
    return subclasses

用法:

>>> import pprint
>>> list_of_classes = get_subclasses(int)
>>> pprint.pprint(list_of_classes)
[<class 'bool'>,
 <enum 'IntEnum'>,
 <enum 'IntFlag'>,
 <class 'sre_constants._NamedIntConstant'>,
 <class 'subprocess.Handle'>,
 <enum '_ParameterKind'>,
 <enum 'Signals'>,
 <enum 'Handlers'>,
 <enum 'RegexFlag'>]

How can I find all subclasses of a class given its name?

We can certainly easily do this given access to the object itself, yes.

Simply given its name is a poor idea, as there can be multiple classes of the same name, even defined in the same module.

I created an implementation for another answer, and since it answers this question and it's a little more elegant than the other solutions here, here it is:

def get_subclasses(cls):
    """returns all subclasses of argument, cls"""
    if issubclass(cls, type):
        subclasses = cls.__subclasses__(cls)
    else:
        subclasses = cls.__subclasses__()
    for subclass in subclasses:
        subclasses.extend(get_subclasses(subclass))
    return subclasses

Usage:

>>> import pprint
>>> list_of_classes = get_subclasses(int)
>>> pprint.pprint(list_of_classes)
[<class 'bool'>,
 <enum 'IntEnum'>,
 <enum 'IntFlag'>,
 <class 'sre_constants._NamedIntConstant'>,
 <class 'subprocess.Handle'>,
 <enum '_ParameterKind'>,
 <enum 'Signals'>,
 <enum 'Handlers'>,
 <enum 'RegexFlag'>]
浊酒尽余欢 2024-10-03 03:03:27

这不像使用 @unutbu 提到的特殊内置 __subclasses__() 类方法那么好,所以我仅将其作为练习。定义的 subclasses() 函数返回一个字典,它将所有子类名称映射到子类本身。

def traced_subclass(baseclass):
    class _SubclassTracer(type):
        def __new__(cls, classname, bases, classdict):
            obj = type(classname, bases, classdict)
            if baseclass in bases: # sanity check
                attrname = '_%s__derived' % baseclass.__name__
                derived = getattr(baseclass, attrname, {})
                derived.update( {classname:obj} )
                setattr(baseclass, attrname, derived)
             return obj
    return _SubclassTracer

def subclasses(baseclass):
    attrname = '_%s__derived' % baseclass.__name__
    return getattr(baseclass, attrname, None)


class BaseClass(object):
    pass

class SubclassA(BaseClass):
    __metaclass__ = traced_subclass(BaseClass)

class SubclassB(BaseClass):
    __metaclass__ = traced_subclass(BaseClass)

print subclasses(BaseClass)

输出:

{'SubclassB': <class '__main__.SubclassB'>,
 'SubclassA': <class '__main__.SubclassA'>}

This isn't as good an answer as using the special built-in __subclasses__() class method which @unutbu mentions, so I present it merely as an exercise. The subclasses() function defined returns a dictionary which maps all the subclass names to the subclasses themselves.

def traced_subclass(baseclass):
    class _SubclassTracer(type):
        def __new__(cls, classname, bases, classdict):
            obj = type(classname, bases, classdict)
            if baseclass in bases: # sanity check
                attrname = '_%s__derived' % baseclass.__name__
                derived = getattr(baseclass, attrname, {})
                derived.update( {classname:obj} )
                setattr(baseclass, attrname, derived)
             return obj
    return _SubclassTracer

def subclasses(baseclass):
    attrname = '_%s__derived' % baseclass.__name__
    return getattr(baseclass, attrname, None)


class BaseClass(object):
    pass

class SubclassA(BaseClass):
    __metaclass__ = traced_subclass(BaseClass)

class SubclassB(BaseClass):
    __metaclass__ = traced_subclass(BaseClass)

print subclasses(BaseClass)

Output:

{'SubclassB': <class '__main__.SubclassB'>,
 'SubclassA': <class '__main__.SubclassA'>}
孤星 2024-10-03 03:03:27

使用__subclasses()__方法的局限性是:

  1. 您需要加载类(即导入它们),否则它将无法工作。
  2. 如果有多层继承,则需要递归。例如:A 类 -> B(A) 类 -> C(B) 类A.__subclasses__() 只会给你类 B 而不是类 C。这是因为类 C 继承自类 B 而不是 A

因此,为了解决这两个限制,除了上面的精彩答案之外,我还有一个稍微不同的方法。

此函数将获取包中的所有类:

def get_classes_from_package_recursively(package: str) -> list[type]:
    """Return a list of classes inside a given package (recurse thorugh any sub-packages).

    Keyword arguments:
    package -- package represented as a string. Must not be relative.
    """
    classes_in_package = []
    # Go through the modules in the package
    for _importer, module_name, is_package in pkgutil.iter_modules(importlib.import_module(package).__path__):
        full_module_name = f"{package}.{module_name}"
        # Recurse through any sub-packages
        if is_package:
            classes_in_subpackage = get_classes_from_package_recursively(package=full_module_name)
            classes_in_package.extend(classes_in_subpackage)

        # Load the module for inspection
        module = importlib.import_module(full_module_name)

        # Iterate through all the objects in the module and
        # using the lambda, filter for class objects and only objects that exist within the module
        for _name, obj in inspect.getmembers(
            module,
            lambda member, module_name=full_module_name: inspect.isclass(member) and member.__module__ == module_name,
        ):
            classes_in_package.append(obj)
    return classes_in_package

如何使用上面的类的示例:

my_classes = get_classes_from_package_recursively(package="src.database.models")

my_classes 现在看起来像这样 [MyModel1、MyModel2、MyModel3 等...]

得到这个后,只需像这样过滤列表:

my_subclasses = [class_ for class_ in my_classes if issubclass(class_, parent_class) and class_ is not parent_class]

The limitations of using the __subclasses()__ approach are:

  1. You need to load the classes (i.e. have them imported), otherwise it will not work.
  2. If you have multiple layers of inheritance, you will need to recurse. For example: class A -> class B(A) -> class C(B). A.__subclasses__() will only give you class B not class C. This is because class C inherits from class B not A!

Therefore, to address these 2 limitations I have a slightly different approach in addition to the awesome answers above.

This function will get all the classes in the package:

def get_classes_from_package_recursively(package: str) -> list[type]:
    """Return a list of classes inside a given package (recurse thorugh any sub-packages).

    Keyword arguments:
    package -- package represented as a string. Must not be relative.
    """
    classes_in_package = []
    # Go through the modules in the package
    for _importer, module_name, is_package in pkgutil.iter_modules(importlib.import_module(package).__path__):
        full_module_name = f"{package}.{module_name}"
        # Recurse through any sub-packages
        if is_package:
            classes_in_subpackage = get_classes_from_package_recursively(package=full_module_name)
            classes_in_package.extend(classes_in_subpackage)

        # Load the module for inspection
        module = importlib.import_module(full_module_name)

        # Iterate through all the objects in the module and
        # using the lambda, filter for class objects and only objects that exist within the module
        for _name, obj in inspect.getmembers(
            module,
            lambda member, module_name=full_module_name: inspect.isclass(member) and member.__module__ == module_name,
        ):
            classes_in_package.append(obj)
    return classes_in_package

Example of how to use the class above:

my_classes = get_classes_from_package_recursively(package="src.database.models")

my_classes will now look like this [MyModel1, MyModel2, MyModel3, etc...]

After getting this, simply filter through the list like this:

my_subclasses = [class_ for class_ in my_classes if issubclass(class_, parent_class) and class_ is not parent_class]
一紙繁鸢 2024-10-03 03:03:27

虽然我非常偏爱 __init_subclass__ 方法,但如果您有一个非常密集的层次结构并且到处都有多重继承,这将保留定义顺序,并避免增长的组合顺序:

def descendents(cls):
    '''Does not return the class itself'''
    R = {}
    def visit(cls):
        for subCls in cls.__subclasses__():
            if not subCls in R:
                R[subCls] = True
                visit(subCls)
    visit(cls)
    return list(R.keys())

这是有效的,因为字典会记住插入顺序他们的钥匙。

While I'm very partial to the __init_subclass__ approach, this will preserve definition order, and avoid combinatorial order of growth if you have a very dense hierarchy with multiple inheritance everywhere:

def descendents(cls):
    '''Does not return the class itself'''
    R = {}
    def visit(cls):
        for subCls in cls.__subclasses__():
            if not subCls in R:
                R[subCls] = True
                visit(subCls)
    visit(cls)
    return list(R.keys())

This works because dictionaries remember the insertion order of their keys.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文