CARVIEW |
__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:
class AsyncInject:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Decorate any items that are 'async def' methods
cls._registry = {}
inject_all = getattr(cls, "_inject_all", False)
for name in dir(cls):
value = getattr(cls, name)
if inspect.iscoroutinefunction(value) and (
inject_all or getattr(value, "_inject", None)
):
setattr(cls, name, _make_method(value))
cls._registry[name] = getattr(cls, name)
# Gather graph for later dependency resolution
graph = {
key: {
p
for p in inspect.signature(method).parameters.keys()
if p != "self" and not p.startswith("_")
}
for key, method in cls._registry.items()
}
cls._graph = graph
As 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:
class MySubClass(AsyncInject, inject_all=True):
...
This doesn't work with my above example, but I could change it to start like this instead:
class AsyncInject:
def __init_subclass__(cls, inject_all=False, **kwargs):
super().__init_subclass__(**kwargs)
# Decorate any items that are 'async def' methods
cls._registry = {}
for name in dir(cls):
value = getattr(cls, name)
if inspect.iscoroutinefunction(value) and (
inject_all or getattr(value, "_inject", None)
):
setattr(cls, name, _make_method(value))
cls._registry[name] = getattr(cls, name)
Further reading
- Use __init_subclass__ hook to validate subclasses via Redowan Delowar
- PEP 487 -- Simpler customisation of class creation
Related
- python Annotated explanation of David Beazley's dataklasses - 2021-12-19
- python Introspecting Python function parameters - 2020-05-27
- datasette Writing a Datasette CLI plugin that mostly duplicates an existing command - 2022-10-22
- pytest Registering temporary pluggy plugins inside tests - 2020-07-21
- python Running Python code in a subprocess with a time limit - 2020-12-06
- python Checking if something is callable or async callable in Python - 2023-08-04
- python Python packages with pyproject.toml and nothing else - 2023-07-07
- python Decorators with optional arguments - 2020-10-28
- datasette Registering new Datasette plugin hooks by defining them in other plugins - 2022-06-17
- pytest Async fixtures with pytest-asyncio - 2022-03-19
Created 2021-12-03T11:19:51-08:00, updated 2021-12-03T11:22:59-08:00 · History · Edit