Programing

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

lottogame 2020. 5. 14. 07:59
반응형

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


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


새로운 스타일의 클래스 (즉 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__()하위 클래스 목록을 반환합니다.


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

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

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

파이썬 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__이 목록에서 하위 클래스 의 동작을 무시하고 하위 클래스를 생략 / 추가 할 수 있습니다 .


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

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

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__()]
        )
    )

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. Thesubclasses()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'>}

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], [])

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

참고URL : https://stackoverflow.com/questions/3862310/how-to-find-all-the-subclasses-of-a-class-given-its-name

반응형