एक वर्ग के सभी उपवर्गों को इसका नाम कैसे दिया जाए?


223

मुझे उन सभी वर्गों को प्राप्त करने के लिए एक कामकाजी दृष्टिकोण की आवश्यकता है जो पायथन में एक आधार वर्ग से विरासत में मिले हैं।

जवाबों:


315

न्यू-स्टाइल क्लासेस (अर्थात, उप-क्लास से object, जो कि पायथन 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__उसे नहीं मिलेगा।


आपने "इसका नाम दिया" का उल्लेख किया है। चूंकि पायथन कक्षाएं प्रथम श्रेणी की वस्तुएं हैं, इसलिए आपको कक्षा के नाम के साथ स्ट्रिंग का उपयोग करने की आवश्यकता नहीं है या ऐसा कुछ भी नहीं है। आप बस सीधे कक्षा का उपयोग कर सकते हैं, और आपको शायद करना चाहिए।

यदि आपके पास एक वर्ग के नाम का प्रतिनिधित्व करने वाला एक स्ट्रिंग है और आप उस वर्ग के उपवर्गों को ढूंढना चाहते हैं, तो दो चरण हैं: वर्ग को उसका नाम दिया गया ढूंढें, और फिर उपवर्गों __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__()फिर उसके उपवर्गों की सूची लौटा देंगे।


मान लीजिए कि मैं एक मॉड्यूल में सभी उपवर्गों को खोजना चाहता था या नहीं, मॉड्यूल के सबमोडुले को आयात किया गया था या नहीं?
सामन्था एटकिंस


धन्यवाद, यही वह है जो मैंने समाप्त कर दिया था, लेकिन उत्सुक था कि क्या इससे बेहतर तरीका हो सकता है कि मैं चूक गया था।
सामन्था एटकिंस

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 __ () आपका समाधान वास्तव में अच्छा है। तुम ले लो;) Btw, मुझे लगता है कि यह आपके मामले में जनरेटर का उपयोग करके अधिक विश्वसनीय हो सकता है।
रोमन प्राइकोचेंको जूल

3
डुप्लिकेट को खत्म करने के all_subclassesलिए एक नहीं होना चाहिए set?
रेन एवरेट

@RyneEverett आप कई विरासत का उपयोग कर रहे हैं, तो आप मतलब है? मुझे लगता है कि अन्यथा आपको डुप्लिकेट के साथ समाप्त नहीं करना चाहिए।
fletom

@ एफलेटम हां, डुप्लिकेट के लिए कई उत्तराधिकार आवश्यक होंगे। उदाहरण के लिए, A(object), B(A), C(A), और D(B, C)get_all_subclasses(A) == [B, C, D, D]
रेन एवरेट

@ रोमनप्रिकोडचेंको: आपके प्रश्न का शीर्षक किसी वर्ग के सभी उपवर्गों को अपना नाम खोजने के लिए कहता है, लेकिन इसके साथ ही अन्य केवल कक्षा को ही दिए गए कार्य को, न कि केवल उसके नाम को - तो बस यह क्या है?
मार्टीन्यू

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
जनरेटर दृष्टिकोण वास्तव में साफ है।
चार43

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अभिभावक की कक्षा में ट्रिगर होगा ।
या डुआन

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
@ क्रिस: एक संस्करण है कि उपयोग नहीं करता है eval()- अब बेहतर है?
मार्टिउ

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], [])
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.