Krateng
Krateng

Reputation: 461

Is there a way to call a method on definition of a subclass in Python?

The __init__ method defines what is done on creating an instance of a class. Can I do something equivalent when a subclass is created?

Let's say I have the abstract class Entity:

class Entity:
    def __onsubclasscreation__(cls):
        for var in cls.__annotations__:
            cls.__dict__[var] = property(lambda self:self.vars[var])

This would mean that whenever I define a new class inheriting from Entity, all annotated variables of that class would receive a getter:

class Train(Entity):
    wagons: int
    color: str

>>> t = Train()
>>> t.vars["wagons"] = 5
>>> t.wagons
5

I can't do this on instantiation because properties need to be defined in the class, and I can't do it in the superclass because I don't know which attributes will be needed. Is there any way to do something dynamically on subclass creation?

Upvotes: 6

Views: 1356

Answers (2)

wim
wim

Reputation: 363103

You are describing the basic usage of __init_subclass__ hook (docs):

Whenever a class inherits from another class, __init_subclass__ is called on that class. This way, it is possible to write classes which change the behavior of subclasses.

>>> class A: 
...     def __init_subclass__(cls): 
...         print(f"init {cls}") 
...
>>> class B(A): 
...     pass 
...
init <class '__main__.B'>

See PEP 487 -- Simpler customisation of class creation for more information.

Note: This is a 3.6+ feature. In older Python versions, use the metaclass __new__ to achieve same:

>>> class MyMeta(type):
...     def __new__(meta, name, bases, class_dict):
...         print("MyMeta.__new__", meta, name, bases, class_dict)
...         return type.__new__(meta, name, bases, class_dict)
...
>>> class A(metaclass=MyMeta):
...     pass
...
MyMeta.__new__ <class '__main__.MyMeta'> A () {'__module__': '__main__', '__qualname__': 'A'}
>>> class B(A):
...     pass
...
MyMeta.__new__ <class '__main__.MyMeta'> B (<class '__main__.A'>,) {'__module__': '__main__', '__qualname__': 'B'}

Upvotes: 9

Green Cloak Guy
Green Cloak Guy

Reputation: 24691

You might be able to just abstract this functionality out into another method and call that method from the superclass's constructor. If subclasses call the superclass's constructor (which they should) then that method will get executed when a subclass is instantiated.

class Entity:
    @classmethod
    def _onsubclasscreation(cls):
        for var in cls.__annotations__:
            cls.__dict__[var] = property(lambda self:self.vars[var])

    def __init__(self):
        ...
        self.__class__._onsubclasscreation()

And then, as long as subclasses don't overwrite the functionality of _onsubclasscreation(), the behavior should be as expected.

Upvotes: 0

Related Questions