Added Module.__check_init__ for after-initialisation checking of invariants. See #472. #492
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
As discussed in #472, it would be nice to have a way to check invariants after a class has been instantiated. Typical use cases are something like:
This PR adds
__check_init__
, which is automatically called after initialisation.Now you may wonder, why not use
__post_init__
, as provided by dataclasses? It turns out that this use-case isn't well-served by__post_init__
. The reason is that__post_init__
is not called if a subclass defines an__init__
or__post_init__
method of its own, and forgets to callsuper().__post_init__()
. Any checks placed in__post_init__
are silently never run!Indeed, it turns out exactly this issue has existed in Diffrax for some time: patrick-kidger/diffrax#308.
The advantage of
__check_init__
is that all instances across the whole MRO are automatically ran. That is, all superclass__check_init__
are automatically ran, without needing to callsuper()
. This makes it impossible for a downstream class to silently avoid checking the invariants of its parent class.Note that
__check_init__
is ran after the class is completely initialised. That is, it is no longer possible to assignself.foo = bar
inside of__check_init__
.This is a deliberate constraint, to prevent parent classes from silently performing any behaviour that may be surprising for its child class. This is particularly important, as a child class has no way to override the
__check_init__
of its parent.As the name suggests, this method is intended for checking invariants, not arbitrary postprocessing. (Use
__post_init__
for that.)