开发者

Python: How to register all child classes with the father class upon creation

开发者 https://www.devze.com 2023-03-21 13:13 出处:网络
I have python class trees, each made up of an abstract base class and many deriving concrete classes. I want all concrete classes to be accessible through a base-class method, and I do not want to spe

I have python class trees, each made up of an abstract base class and many deriving concrete classes. I want all concrete classes to be accessible through a base-class method, and I do not want to specify anything during child-class creation.

This is what my imagined solution looks like:

class BaseClassA(object):
    # <some magic code around here>
    @classmethod
    def getConcreteClasses(cls):
        # <some magic related code here>

class ConcreteClassA1(BaseClassA):
    # no magic-related code here

class ConcreteClassA2(BaseClassA):
    # no magic-related code here

As much as possible, I'd prefer to write the "magic" once as a sort of design pa开发者_Python百科ttern. I want to be able to apply it to different class trees in different scenarios (i.e. add a similar tree with "BaseClassB" and its concrete classes).

Thanks Internet!


you can use meta classes for that:

class AutoRegister(type):
    def __new__(mcs, name, bases, classdict):
        new_cls = type.__new__(mcs, name, bases, classdict)
        #print mcs, name, bases, classdict
        for b in bases:
            if hasattr(b, 'register_subclass'):
                b.register_subclass(new_cls)
        return new_cls


class AbstractClassA(object):
    __metaclass__ = AutoRegister
    _subclasses = []

    @classmethod
    def register_subclass(klass, cls):
        klass._subclasses.append(cls)

    @classmethod
    def get_concrete_classes(klass):
        return klass._subclasses


class ConcreteClassA1(AbstractClassA):
    pass

class ConcreteClassA2(AbstractClassA):
    pass

class ConcreteClassA3(ConcreteClassA2):
    pass


print AbstractClassA.get_concrete_classes()

I'm personnaly very wary of this kind of magic. Don't put too much of this in your code.


Here is a simple solution using modern python's (3.6+) __init__subclass__ defined in PEP 487. It allows you to avoid using a meta-class.

class BaseClassA(object):
    _subclasses = [] 

    @classmethod
    def get_concrete_classes(cls):
        return list(cls._subclasses)

    def __init_subclass__(cls):
        BaseClassA._subclasses.append(cls)


class ConcreteClassA1(BaseClassA):
    pass  # no magic-related code here


class ConcreteClassA2(BaseClassA):
    pass  # no magic-related code here


print(BaseClassA.get_concrete_classes())


You should know that part of the answer you're looking for is built-in. New-style classes automatically keep a weak reference to all of their child classes which can be accessed with the __subclasses__ method:

@classmethod
def getConcreteClasses(cls):
    return cls.__subclasses__()

This won't return sub-sub-classes. If you need those, you can create a recursive generator to get them all:

@classmethod
def getConcreteClasses(cls):
    for c in cls.__subclasses__():
        yield c
        for c2 in c.getConcreteClasses():
            yield c2


Another way to do this, with a decorator, if your subclasses are either not defining __init__ or are calling their parent's __init__:

def lister(cls):
    cls.classes = list()
    cls._init = cls.__init__
    def init(self, *args, **kwargs):
        cls = self.__class__
        if cls not in cls.classes:
            cls.classes.append(cls)
        cls._init(self, *args, **kwargs)
    cls.__init__ = init
    @classmethod
    def getclasses(cls):
        return cls.classes
    cls.getclasses = getclasses
    return cls

@lister
class A(object): pass

class B(A): pass

class C(A):
    def __init__(self):
        super(C, self).__init__()

b = B()
c = C()
c2 = C()
print 'Classes:', c.getclasses()

It will work whether or not the base class defines __init__.

0

精彩评论

暂无评论...
验证码 换一张
取 消