Search code examples
pythonclasssubclassintrospection

Execute code when extending a class in Python


Is there a way to execute some code in Python when a class is extended by another one? More precisely, I would like to automatically keep a register of all the classes extending a base class. Therefore, I would like to write something like:

class Base:

    subclasses = {}

    @classmethod
    def extending(cls, subclass):
        cls.subclasses[subclass.__name__] = subclass

class Extend1(Base):
    pass

class Extend2(Base):
    pass

After this piece of code, I would expect Base.subclasses to contain links to the two extending classes.

Can you suggest a way to make this work? Or, maybe, provide an equivalent solution?


Solution

  • This is clearly an application for metaclasses. That is if you want to change the behavior of a classe C then you should change the class of C itself which is a metaclass, usually type. Here is a stub of solution:

    class Meta(type):
        def __new__(cls, clsname, bases, dct):
            res = type.__new__(cls, clsname, bases, dct)
            for cls in bases:
                if isinstance(cls, Meta):
                    try:
                        cls.extending(res)
                    except AttributeError:
                        pass
            return res
    

    I'm inheriting from type to make a metaclass intercepting the creation of the class and storing the created class in the base _subclass attribute. Then with

    class Base(object):
        __metaclass__ = Meta
    
        subclasses = {}
    
        @classmethod
        def extending(cls, subclass):
            cls.subclasses[subclass.__name__] = subclass
    
    class Extend1(Base):
        pass
    
    class Extend2(Base):
        pass
    

    Then

    >>> Base.subclasses
    {'Extend1': __main__.Extend1, 'Extend2': __main__.Extend2}
    

    A very important point: metaclass are inherited (I think this is not the exactly the proper term here). Therefore Extend1 and Extend2 are also instances of Meta:

    >>> type(Extend1)
    <class '__main__.Meta'>