Understanding __init_subclass__
Solution 1:
PEP 487 sets out to take two common metaclass usecases and make them more accessible without having to understand all the ins and outs of metaclasses. The two new features, __init_subclass__
and __set_name__
are otherwise independent, they don't rely on one another.
__init_subclass__
is just a hook method. You can use it for anything you want. It is useful for both registering subclasses in some way, and for setting default attribute values on those subclasses.
We recently used this to provide 'adapters' for different version control systems, for example:
class RepositoryType(Enum):
HG = auto()
GIT = auto()
SVN = auto()
PERFORCE = auto()
class Repository():
_registry = {t: {} for t in RepositoryType}
def __init_subclass__(cls, scm_type=None, name=None, **kwargs):
super().__init_subclass__(**kwargs)
if scm_type is not None:
cls._registry[scm_type][name] = cls
class MainHgRepository(Repository, scm_type=RepositoryType.HG, name='main'):
pass
class GenericGitRepository(Repository, scm_type=RepositoryType.GIT):
pass
This trivially let us define handler classes for specific repositories without having to resort to using a metaclass or decorators.
Solution 2:
__init_subclass__
and __set_name__
are orthogonal mechanisms - they're not tied to each other, just described in the same PEP. Both are features that needed a full-featured metaclass before. The PEP 487 addresses 2 of the most common uses of metaclasses:
- how to let the parent know when it is being subclassed (
__init_subclass__
) - how to let a descriptor class know the name of the property it is used for (
__set_name__
)
As the PEP says:
While there are many possible ways to use a metaclass, the vast majority of use cases falls into just three categories: some initialization code running after class creation, the initialization of descriptors and keeping the order in which class attributes were defined.
The first two categories can easily be achieved by having simple hooks into the class creation:
- An
__init_subclass__
hook that initializes all subclasses of a given class.- upon class creation, a
__set_name__
hook is called on all the attribute (descriptors) defined in the class, andThe third category is the topic of another PEP, PEP 520.
Notice also, that while __init_subclass__
is a replacement for using a metaclass in this class's inheritance tree, __set_name__
in a descriptor class is a replacement for using a metaclass for the class that has an instance of the descriptor as an attribute.