How to find all the subclasses of a class given its name?

后端 未结 10 1694
遥遥无期
遥遥无期 2020-11-22 10:14

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

相关标签:
10条回答
  • 2020-11-22 10:53

    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'>]
    
    0 讨论(0)
  • 2020-11-22 10:55

    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
    
    0 讨论(0)
  • 2020-11-22 11:01

    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.

    0 讨论(0)
  • 2020-11-22 11:02

    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'>}
    
    0 讨论(0)
  • 2020-11-22 11:05

    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], [])
    
    0 讨论(0)
  • 2020-11-22 11:07

    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__()]
            )
        )
    
    0 讨论(0)
提交回复
热议问题