কোনও শ্রেণীর নাম অনুসারে সমস্ত সাবক্লাস কীভাবে সন্ধান করবেন?


223

পাইথনের বেস ক্লাস থেকে উত্তরাধিকারসূত্রে প্রাপ্ত সমস্ত ক্লাস পাওয়ার জন্য আমার একটি কার্যকরী পদ্ধতির প্রয়োজন।

উত্তর:


314

নতুন-স্টাইলের ক্লাসগুলি (অর্থাত্ উপক্লাসেড 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__()তারপরে তার সাবক্লাসগুলির একটি তালিকা ফিরিয়ে আনবেন।


ধরুন আমি একটি মডিউলে সমস্ত সাবক্লাসটি খুঁজতে চেয়েছিলাম এটির সাথে থাকা মডিউলের সাবমোডিয়ালটি আমদানি করা হয়েছিল কিনা?
সামান্থা অ্যাটকিনস

1
@ সামান্থা অ্যাটকিনস: প্যাকেজের সমস্ত সাবমোডিয়ুলের একটি তালিকা তৈরি করুন এবং তারপরে প্রতিটি মডিউলের জন্য সমস্ত শ্রেণীর একটি তালিকা তৈরি করুন ।
unutbu

ধন্যবাদ, এটিই আমি শেষ করেছিলাম তবে কৌতূহল ছিল যদি এর থেকে আরও ভাল উপায় আমি মিস করি।
সামান্থা অ্যাটকিন্স

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
ধন্যবাদ @ ফ্লেটম! যদিও সেই দিনগুলিতে আমার যা দরকার ছিল তা কেবল __শাসক্লাস __ () ছিল আপনার সমাধানটি সত্যিই দুর্দান্ত। আপনাকে +1;) নিন, বিটিডব্লু, আমি মনে করি এটি আপনার ক্ষেত্রে জেনারেটর ব্যবহার করে আরও নির্ভরযোগ্য হতে পারে।
রোমান Prykhodchenko

3
সদৃশগুলি মুছে ফেলার জন্য all_subclassesএকটি হওয়া উচিত নয় set?
রাইনে এভারেট

@ রেইনরেট বলতে কী বোঝাতে চাইছেন আপনি যদি একাধিক উত্তরাধিকার ব্যবহার করছেন? আমি মনে করি অন্যথায় আপনার নকল দিয়ে শেষ করা উচিত নয়।
ফলম

@ ফ্লেটম হ্যাঁ, নকলের জন্য একাধিক উত্তরাধিকার প্রয়োজন। উদাহরণস্বরূপ, A(object), B(A), C(A), এবং D(B, C)get_all_subclasses(A) == [B, C, D, D]
Ryne এভারেট

@ রোমানপ্রিখোডচেনকো: আপনার প্রশ্নের শিরোনামে বলা হয়েছে যে একটি শ্রেণীর সমস্ত সাবক্লাসের নাম দেওয়া হয়েছে, তবে এটি কেবল তার নামই নয়, কেবল শ্রেণীর নিজের দেওয়া কাজ - কেবল এটি কী?
মার্টিনিউ

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
জেনারেটর পদ্ধতির সত্যিই পরিষ্কার।
চারটি

22

পাইথন ৩.6 -__init_subclass__

অন্যান্য উত্তর হিসাবে উল্লিখিত হিসাবে আপনি __subclasses__উপশ্রেণীর তালিকা পেতে বৈশিষ্ট্যটি পরীক্ষা করতে পারেন , পাইথন ৩.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']- সুতরাং আমার পোস্টের প্রাথমিক বিন্দু আর প্রযোজ্য নয়।

এফডব্লিউআইডাব্লু, এখানে কেবলমাত্র স্থানীয়ভাবে সংজ্ঞায়িত শ্রেণীর সাথে কাজ করা @ আনটবুর উত্তর সম্পর্কে আমি কী বোঝাতে চাইছিলাম - এবং এর 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()- এখন আরও ভাল?
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__()ক্লাস পদ্ধতিটি ব্যবহার করার মতো উত্তম উত্তর নয় যা @ ইউন্টবুবু উল্লেখ করেছেন, তাই আমি এটিকে নিছক অনুশীলন হিসাবে উপস্থাপন করি। 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.