7.1. Metaprogramming Init Subclass

  • Created for the people, to reduce times when you need metaclasses

>>> class Account:
...     def __init_subclass__(cls, **kwargs):
...         super().__init_subclass__(**kwargs)

7.1.1. Example

>>> class Account:
...     def __init_subclass__(cls, **kwargs):
...         print('Account init_subclass')
...
>>> class User(Account):
...    pass
...
Account init_subclass

7.1.2. Arguments

>>> class Account:
...     def __init_subclass__(cls, debug=False, **kwargs):
...         print(f'Account init_subclass: {debug=}')
...

Creating class without specifying the argument will use the default value:

>>> class User(Account):
...    pass
...
Account init_subclass: debug=False

Now, let's pass the argument to the subclass:

>>> class User(Account, debug=True):
...    pass
...
Account init_subclass: debug=True

7.1.3. Use Case - 1

>>> from uuid import uuid4
>>> from datetime import datetime, timezone
>>> from pprint import pprint
>>> class Account:
...     def __init_subclass__(cls, debug=False, **kwargs):
...         if debug:
...             cls._uuid = uuid4()
...             cls._since = datetime.now(timezone.utc)
...
>>> class User(Account):
...     pass
...
>>> pprint(vars(User))
mappingproxy({'__doc__': None,
              '__firstlineno__': 1,
              '__module__': '__main__',
              '__static_attributes__': ()})
>>> class User(Account, debug=True):
...     pass
...
>>> pprint(vars(User))  
mappingproxy({'__doc__': None,
              '__module__': '__main__',
              '_since': datetime.datetime(2024, 8, 20, 14, 55, 59, 764071, tzinfo=datetime.timezone.utc),
              '_uuid': UUID('6d1bc92c-ddb6-47cb-a872-24f94f3d1be4')})

7.1.4. Use Case - 2

>>> import string
>>>
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>>
>>> tuple(string.ascii_uppercase)
('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')
>>> class Account:
...     def __init_subclass__(cls, strict=False, **kwargs):
...         if strict:
...             uppercase = tuple(string.ascii_uppercase)
...             assert cls.__name__.startswith(uppercase), \
...             'Class name must start with uppercase letter'
...         super().__init_subclass__()
>>> class user(Account):
...     pass
>>> class user(Account, strict=True):
...     pass
...
Traceback (most recent call last):
AssertionError: Class name must start with uppercase letter
>>> class User(Account):
...     pass
>>> class User(Account, strict=True):
...     pass

7.1.5. Use Case - 3

>>> from pprint import pprint
>>>
>>>
>>> class PositionMixin:
...     def __init_subclass__(cls, **kwargs):
...         cls.position_x = 0
...         cls.position_y = 0
...         super().__init_subclass__(**kwargs)
>>>
>>>
>>> class HealthMixin:
...     def __init_subclass__(cls, **kwargs):
...         cls.health = 100
...         super().__init_subclass__(**kwargs)
>>>
>>>
>>> class Hero(PositionMixin, HealthMixin):
...     def __init__(self, name):
...         self.name = name
>>> hero = Hero('Twardowski')
>>>
>>> hero.position_x
0
>>> hero.position_y
0
>>> hero.health
100
>>> vars(hero)
{'name': 'Twardowski'}
>>> pprint(vars(Hero))  
mappingproxy({'__doc__': None,
              '__firstlineno__': 1,
              '__init__': <function Hero.__init__ at 0x...>,
              '__module__': '__main__',
              '__static_attributes__': ('name',),
              'health': 100,
              'position_x': 0,
              'position_y': 0})