이름이 주어진 클래스의 모든 서브 클래스를 찾는 방법은 무엇입니까?


223

파이썬의 기본 클래스에서 상속 된 모든 클래스를 가져 오는 효과적인 접근 방식이 필요합니다.

답변:


315

새로운 스타일의 클래스 (즉 object, Python 3에서 기본값 인 from에서 서브 클래 싱됨)에는 __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__않으며 찾을 수 없습니다.


당신은 "이름을 지어주었습니다"라고 언급했습니다. 파이썬 클래스는 일류 객체이므로 클래스 대신 클래스 이름을 가진 문자열을 사용할 필요가 없습니다. 클래스를 직접 사용할 수 있으며 아마도해야합니다.

클래스 이름을 나타내는 문자열이 있고 해당 클래스의 서브 클래스를 찾으려면 두 단계가 있습니다. 이름이 지정된 클래스를 찾은 다음 __subclasses__위와 같이 서브 클래스를 찾으십시오 .

이름에서 수업을 찾는 방법은 원하는 수업에 따라 다릅니다. 클래스를 찾으려고하는 코드와 동일한 모듈에서 찾을 것으로 예상되면

cls = globals()[name]

일을 할 수도 있고, 지역 주민에게서 찾을 것으로 예상되는 경우에는

cls = locals()[name]

클래스가 모든 모듈에있을 수있는 경우 이름 문자열에는 'pkg.module.Foo'just 대신에 정규화 된 이름이 포함되어야합니다 'Foo'. importlib클래스 모듈을로드 한 다음 해당 속성을 검색하는 데 사용하십시오 .

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

그러나 클래스를 찾으면 cls.__subclasses__()하위 클래스 목록을 반환합니다.


모듈을 포함하는 모듈의 하위 모듈을 가져 왔는지 여부에 상관없이 모듈에서 모든 하위 클래스를 찾고 싶다고 가정 해 봅시다.
사만다 앳킨스


고마워, 그게 내가 끝났지 만 내가 놓친 더 좋은 방법이 있는지 궁금합니다.
사만다 앳킨스

63

직접 서브 클래스를 원한다면 .__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

3
@fletom 감사합니다! 그 당시에 필요한 것은 __subclasses __ () 였지만 솔루션은 정말 좋습니다. +1;) Btw, 당신의 경우 발전기를 사용하는 것이 더 신뢰할 수 있다고 생각합니다.
로마 Prykhodchenko

3
all_subclassesA가 될 set중복을 제거하기 위해?
Ryne Everett

@RyneEverett 다중 상속을 사용하고 있다는 의미입니까? 그렇지 않으면 중복으로 끝나서는 안된다고 생각합니다.
fletom

@fletom 예, 중복에는 여러 상속이 필요합니다. 예를 들어, A(object), B(A), C(A),와 D(B, C). get_all_subclasses(A) == [B, C, D, D].
Ryne Everett

@RomanPrykhodchenko : 질문의 제목은 클래스의 이름이 주어진 클래스의 모든 서브 클래스를 찾는 것입니다. 그러나 이것뿐만 아니라 클래스 자체에 주어진 이름뿐만 아니라 다른 클래스의 작업 만이 있습니다.
martineau

33

일반적인 형태의 가장 간단한 솔루션 :

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

2
발전기 접근 방식은 정말 깨끗합니다.
four43

22

파이썬 3.6 -__init_subclass__

다른 답변에서 언급했듯이 __subclasses__하위 클래스 목록을 가져 오기 위해 속성을 확인할 수 있습니다. 파이썬 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__이 목록에서 하위 클래스 의 동작을 무시하고 하위 클래스를 생략 / 추가 할 수 있습니다 .


1
예, 모든 종류의 하위 클래스 __init_subclass는 부모 클래스를 트리거합니다 .
또는 Duan

9

참고 : @unutbu가 아닌 누군가가 더 이상 사용하지 않도록 참조 답변을 변경 vars()['Foo']했으므로 내 게시물의 기본 사항이 더 이상 적용되지 않습니다.

FWIW, @unutbu의 답변 은 로컬로 정의 된 클래스에서만 작동하며 그 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'>]

1
@Chris : eval()더 이상 사용하지 않는 버전을 추가 했습니까?
martineau

4

모든 서브 클래스 목록을 얻기위한 훨씬 짧은 버전 :

from itertools import chain

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

2

이름이 지정된 클래스의 모든 하위 클래스를 어떻게 찾을 수 있습니까?

객체 자체에 대한 액세스 권한이 주어지면 확실히 쉽게 할 수 있습니다.

단순히 같은 모듈에 정의 된 동일한 이름의 여러 클래스가있을 수 있기 때문에 단순히 이름을 지정하는 것은 좋지 않습니다.

다른 답변에 대한 구현을 만들었 으며이 질문에 대답하고 다른 솔루션보다 약간 우아하기 때문에 여기에 있습니다.

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'>]

2

이것은 __subclasses__()@unutbu가 언급 한 특수 내장 클래스 메소드를 사용하는 것만 큼 좋은 대답이 아니므로 연습으로 제시합니다. 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'>}

1

재귀없는 버전은 다음과 같습니다.

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], [])
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.