Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Module.__check_init__ for after-initialisation checking of invariants. See #472. #492

Merged
merged 1 commit into from
Sep 11, 2023

Conversation

patrick-kidger
Copy link
Owner

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:

class A(eqx.Module):
    x: int

    def __check_init__(self):
        if x < 0:
            raise ValueError("x must be non-negative")

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 call super().__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 call super(). 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 assign self.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.)

@patrick-kidger patrick-kidger merged commit c84fd51 into dev Sep 11, 2023
2 checks passed
@patrick-kidger patrick-kidger deleted the check-init branch September 11, 2023 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant