Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
init_subclass#
David Beazley on Twitter said:
I think 95% of the problems once solved by a metaclass can be solved by
__init_subclass__instead
This inspired me to finally learn how to use it! I used my asyncinject project as an experimental playground.
The __init_subclass__ class method is called when the class itself is being constructed. It gets passed the cls and can make modifications to it.
Here’s the pattern I used:
1class AsyncInject:2 def __init_subclass__(cls, **kwargs):3 super().__init_subclass__(**kwargs)4 # Decorate any items that are 'async def' methods5 cls._registry = {}6 inject_all = getattr(cls, "_inject_all", False)7 for name in dir(cls):8 value = getattr(cls, name)9 if inspect.iscoroutinefunction(value) and (10 inject_all or getattr(value, "_inject", None)11 ):12 setattr(cls, name, _make_method(value))13 cls._registry[name] = getattr(cls, name)14 # Gather graph for later dependency resolution15 graph = {16 key: {17 p18 for p in inspect.signature(method).parameters.keys()19 if p != "self" and not p.startswith("_")20 }21 for key, method in cls._registry.items()22 }23 cls._graph = graphAs you can see, it’s using getattr() and setattr() against the cls object to make modifications to the class - in this case it’s running a decorator against various methods and adding two new class properties, _registry and _graph.
The **kwargs thing there is interesting: you can define keyword arguments and use them when you subclass, like this:
1class MySubClass(AsyncInject, inject_all=True):2 ...This doesn’t work with my above example, but I could change it to start like this instead:
1class AsyncInject:2 def __init_subclass__(cls, inject_all=False, **kwargs):3 super().__init_subclass__(**kwargs)4 # Decorate any items that are 'async def' methods5 cls._registry = {}6 for name in dir(cls):7 value = getattr(cls, name)8 if inspect.iscoroutinefunction(value) and (9 inject_all or getattr(value, "_inject", None)10 ):11 setattr(cls, name, _make_method(value))12 cls._registry[name] = getattr(cls, name)