मुझे उन सभी वर्गों को प्राप्त करने के लिए एक कामकाजी दृष्टिकोण की आवश्यकता है जो पायथन में एक आधार वर्ग से विरासत में मिले हैं।
मुझे उन सभी वर्गों को प्राप्त करने के लिए एक कामकाजी दृष्टिकोण की आवश्यकता है जो पायथन में एक आधार वर्ग से विरासत में मिले हैं।
जवाबों:
न्यू-स्टाइल क्लासेस (अर्थात, उप-क्लास से 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__()
फिर उसके उपवर्गों की सूची लौटा देंगे।
यदि आप सीधे उपवर्ग चाहते हैं तो .__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
all_subclasses
लिए एक नहीं होना चाहिए set
?
A(object)
, B(A)
, C(A)
, और D(B, C)
। get_all_subclasses(A) == [B, C, D, D]
।
सामान्य रूप में सबसे सरल समाधान:
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
__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__
इस सूची से उप-वर्ग को जोड़ने और छोड़ने या छोड़ने के व्यवहार को ओवरराइड कर सकते हैं ।
__init_subclass
अभिभावक की कक्षा में ट्रिगर होगा ।
नोट: मैं देख रहा हूं कि किसी (@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'>]
eval()
- अब बेहतर है?
सभी उपवर्गों की सूची प्राप्त करने के लिए एक छोटा संस्करण:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
मैं किसी वर्ग के सभी उपवर्गों को इसका नाम कैसे दे सकता हूं?
हम निश्चित रूप से इसे आसानी से ऑब्जेक्ट तक पहुँच प्रदान कर सकते हैं, हाँ।
बस इसका नाम दिया गया एक खराब विचार है, क्योंकि एक ही नाम के कई वर्ग हो सकते हैं, यहां तक कि एक ही मॉड्यूल में भी परिभाषित किया जा सकता है।
मैंने एक और उत्तर के लिए एक कार्यान्वयन बनाया , और चूंकि यह इस प्रश्न का उत्तर देता है और यह अन्य समाधानों की तुलना में थोड़ा अधिक सुरुचिपूर्ण है, यहाँ यह है:
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'>]
यह विशेष उत्तर-निर्मित __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'>}
यहाँ पुनरावृत्ति के बिना एक संस्करण है:
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], [])