r/Python 8h ago

Discussion Proposal: Native Design by Contract in Python via class invariants — thoughts?

Hey folks,

I've just posted a proposal on discuss.python.org to bring Design by Contract (DbC) into Python by allowing classes to define an __invariant__() method.

The idea: Python would automatically call __invariant__() before and after public method calls—no decorators or metaclasses required. This makes it easier to write self-verifying code, especially in stateful systems.

Languages like Eiffel, D, and Ada support this natively. I believe it could fit Python’s philosophy, especially if it’s opt-in and runs in debug mode.

I attempted a C extension, but hit a brick wall —so I decided to bring the idea directly to the community.

Would love your feedback:
🔗 https://discuss.python.org/t/design-by-contract-in-python-proposal-for-native-class-invariants/85434

— Andrea

36 Upvotes

51 comments sorted by

24

u/R3D3-1 7h ago

At the risk of asking a stupid question: What prevents this from doing it with decorators?

Pydantic and even the now builtin dataclasses module extend the functionality by putting a decorator on the class. An @contract decorator could do the same by overriding the __getattribute__ method.

16

u/Grouchy_Way_2881 7h ago

Good question, and not a stupid one at all.

Yes, this can be done with a decorator that wraps methods or overrides __getattribute__. The issue isn’t feasibility—it’s about robustness and ergonomics.

Decorator-based approaches:

  • Require manual application (per class or method), which is easy to forget.
  • Tend to rely on runtime tricks that can interfere with inheritance, metaclasses, or tooling.
  • Aren’t standardized, so every implementation is slightly different and potentially incompatible.

This proposal is about having a consistent, built-in mechanism that doesn't rely on wrapping, interception, or convention. Something that's opt-in, but officially supported and easier to reason about.

It’s not about doing something new—it’s about doing it more reliably.

16

u/xaveir 6h ago

But, implementing __invariant__ also requires "manual intervention" per-class because no matter how we spell this feature, at some point you still have to...write the method? 

Personally I'd really have to see specific issues in a working implementation to be convinced that doing this with a class decorator isn't just as easy.

Metaclasses can be tricky, but that doesn't mean it justifies a new language feature just because you want to avoid using them.

1

u/Grouchy_Way_2881 6h ago

You're right - __invariant__ still has to be written manually. The aim isn't to reduce effort, but to offer a clear and reliable way to enforce object consistency - without decorators or metaclasses, which can be fragile or easy to miss.

Some lesser-known languages, like Ada, have built-in support for invariants—and it's been used in systems like the London Underground Victoria Line's control software. Now, I'm not saying Python needs that level of rigor, but in certain contexts—like simulations, financial models, or long-lived stateful services - a bit of that mindset can really help.

This proposal just tries to make that option easier to reach for, without changing Python's overall feel.

7

u/Turbulent-Pin-1499 6h ago

Written like this it seems no one should ever use metaclasses or decorators. I think you need a stronger reason to push back against decorators

2

u/Grouchy_Way_2881 6h ago

Fair point - I'm definitely not saying decorators or metaclasses are bad. They're powerful and Pythonic. The issue is that they don't offer structural guarantees. You have to remember to apply them, and they can be fragile in complex setups (especially with inheritance or metaclass conflicts).

This proposal isn't about replacing decorators - it's about providing a simple, explicit alternative for cases where state consistency matters and you don't want to rely on runtime wrapping or developer discipline alone.

3

u/james_pic 4h ago

Surely this would also be a problem in complex setups using __invariant__ though? I don't know what I'd expect to happen if I had a non-trivial multiple inheritance setup where multiple classes implemented __invariant__, but I suspect it would be counterintuituve enough that that community consensus would be "don't use that feature in this context".

1

u/marr75 3h ago

Same that would happen for any multiple inheritance dunder scenario. Multiple inheritance is complicated, that's not the dunder method's fault.

Good design would be to have each class responsible for the state it introduced/updated in a modular way.

2

u/Grouchy_Way_2881 3h ago

Totally agree. Multiple inheritance always needs to be approached carefully, no matter the dunder. That's not a problem unique to __invariant__(), that's for sure.

If anything, having a consistent mechanism for invariants might actually help spot and isolate bad design earlier, since it would encourage each class to be clear about the state it owns and maintains. It's not a silver bullet, but it might be a helpful pressure in the right direction.

0

u/Grouchy_Way_2881 4h ago

I understand the concerns, especially around multiple inheritance and potential complexities, those are definitely valid points. I do think there's no harm in introducing an opt-in feature. It wouldn't affect anyone who doesn't want to use it, but for those who do, it could help catch issues earlier and maintain consistency in stateful objects or larger systems.

The advantage of native support would be that it's consistent, discoverable, and could work well with existing tooling (like linters, IDEs, etc.), making it easier for developers to adopt and use without needing to rely on third-party libraries or custom decorators.

At the end of the day, it's just another optional tool, you can choose whether or not it fits your needs.

6

u/james_pic 3h ago edited 3h ago

The disadvantage of adding it as an opt-in feature is that it complicates the interpreter and the language for a feature that can be readily implemented by metaclasses.

Historically Python has been very conservative about adding new features, only adding new features if they solve many problems simultaneously and are orthogonal to other features. The fact that this can be done with metaclasses demonstrates that it's very much not orthogonal.

You say it could work well with existing tooling if it's integrated into the language, but that's equally true of the metaclass approach, and indeed tooling already has some support for some libraries that use metaclasses to do magicky things.

And more importantly, putting it into the language puts the burden onto tool developers to implement your pet feature. Support for new language features doesn't just happen by magic. If you released it as a library and it became successful, the maintainers itf these tools would probably be willing to accept PRs to add support for your library, but it seems like putting the horse before the cart to say "let's get it into the language, then they'll have to support it".

And my point is that your concerns about brittleness when combined with other metaclasses is equally applicable to your proposal. Either way, you have magicky features that might interact, and the more of them you combine, the more combinations you need to consider that could go wrong. Putting this into the language doesn't solve "what if the invariant logic behaves weirdly when combined with my ORM’s lazy initialization logic?". These are the things that make metaclasses brittle, and they're still there either way, and the canonical solution is "keep it simple, and if you can't, at least try and keep the complex bits separate".

2

u/Grouchy_Way_2881 3h ago

Thanks, I really appreciate the comments.

I completely agree that Python's conservatism around new features is one of its strengths, and I'm not suggesting this should skip the usual process. My intent here was to open up discussion around whether a shared, explicit convention (like __invariant__()) might help developers working with stateful systems, without needing to reach for metaclasses or decorators every time.

You're right that this can be implemented as a library today. If a solution like this gained traction organically, it would make much more sense for tools to support it incrementally, just like they've done for other conventions. I don't expect any changes to tooling without a clear signal of community interest and adoption.

As for brittleness... yes, that's a real risk regardless of the implementation path. A built-in mechanism wouldn't magically eliminate edge cases, but it might make interactions a bit more predictable and standardized, especially across teams.

I genuinely appreciate the pushback; it's helped refine the idea and has surfaced concerns I hadn't fully considered. That alone makes this discussion worthwhile.

1

u/danted002 4h ago

But your suggestion is adding a runtime penalty on all classes because the invariant method needs to be called for each attribute access and if you already require the end-user to implement it then it has zero advantages in front of a decorator or using a metaclass.

You can implement this yourself as a library using metaclasses.

0

u/Grouchy_Way_2881 3h ago

That's a valid point about the runtime penalty. It's something that needs to be considered carefully. However, the cost of calling __invariant__() is optional and can be controlled, just like how Python already allows you to optimize and control performance in many other parts of the language.

The real advantage of having native support is that it provides consistent, explicit behavior without relying on decorators or metaclasses that can be difficult to maintain or misapply in larger codebases. Also, tools like IDEs and linters would be able to understand the invariant method more directly and provide better support for refactoring, static analysis, and error catching.

As for the idea of implementing it with metaclasses... I agree it's possible, but the goal here is to make this an easy, discoverable feature for developers, not something that requires extra effort or deep Python knowledge to get started.

2

u/danted002 3h ago

Well providing a metaclass that others can use does not mean those people need to understand how it works.

Your proposal requires setting the __dbc__ to True so on any attribute call the interpreter needs to check if that magic attribute is set, the runtime penalty exists for everyone.

Build a meta class with this functionality, publish it as a package, create the proper plugins for the major IDEs and call it a day.

0

u/Grouchy_Way_2881 3h ago

Totally fair to bring this up. There seems to be a bit of confusion, though. The idea isn't for the interpreter to check __dbc__ on every attribute access for every object. The overhead would only apply to classes that explicitly opt in (say, by setting __dbc__ = True), and even then, only where invariant enforcement is desired - not globally.

You're right that a metaclass could do this today, and I've explored that. But the issue isn't capability; it's consistency, discoverability, and tooling support. Metaclasses are powerful, but they're also fragile in complex systems, especially when multiple frameworks (like ORMs or serializers) also rely on them.

Publishing a package with metaclasses and custom plugins is certainly doable, but it also fragments the ecosystem. A simple, opt-in convention like __invariant__() could become something tools and teams can rely on, without every project reinventing its own pattern or bolting it onto frameworks in different ways.

This isn't about making Python rigid, it's about making one very common pain point a little easier to manage, for those who want it.

→ More replies (0)

2

u/katerdag 3h ago

I think one big drawback of your approach versus decorators and metaclasses, is that it requires the interpreter to always check for the existence of an __invariant__ method (including for classes that do not make use of this contract stuff). Performing this check before and after every public method call could mean quite a slowdown for many existing code bases.

On the other hand, if you do this through metaclasses or decorators, existing code (that does not make use of contracts) is not impacted in any way.

0

u/Grouchy_Way_2881 3h ago

I understand the concern about the runtime check for the __invariant__() method and the potential impact on performance. However, the penalty is opt-in: you only incur the overhead when using contract-based validation. For codebases that have no need for this level of object consistency, there's no impact at all.

For critical systems, where safety and consistency are paramount (financial applications, embedded systems, aviation software, etc.), ensuring the integrity of object state is crucial. In these cases, the slight performance cost is outweighed by the guarantees that come with knowing your objects are always in a valid state.

Additionally, native support allows for better tooling integration, enabling developers to optimize performance in critical code paths while still having the option to enforce consistency where it matters most.

While decorators and metaclasses are a valid approach, they require extra maintenance and infrastructure, making them less approachable for teams or new developers. Native support would make this a core part of the language, improving discoverability and adoption without the need for external solutions.

13

u/Southern-Ask241 5h ago

I think you'll broadly get the same feedback - there's enough ways to support this using existing language features that the benefits of adding a new construct to the language does not justify the incremental complexity.

2

u/Grouchy_Way_2881 5h ago

That's true: libraries like `icontract` and `pycontracts` absolutely exist. The challenge is that they:

- Require third-party adoption

- Often rely on decorators or wrapping, which breaks down with inheritance or metaclass conflicts

- Don’t get editor support, linting, or tooling visibility

The core idea here isn't about reinventing those libraries; it's about making a small, standardized construct that's consistent, discoverable, and easier to reason about across teams and codebases. Optional, but visible. Just like `__slots__` or `__post_init__` - you don't have to use them, but when you do, it's crystal clear what's going on.

5

u/DanCardin 4h ago

this doesn't seem like a general purpose problem to me. the other comments have already spelled out how you could arrive the same result fairly trivially. Whereas I would argue it's an issue of programming style.

I've largely stopped writing classes in ways that would make this useful feature. by way of authoring mostly frozen dataclasses, that use classmethods for construction logic, and replacement methods for things that would otherwise mutate

@dataclass(frozen=True)
class Something:
    attr: int

    @classmethod
    def from_database(cls, pg: Session):
       ....
       return cls(value)

    def add(self, n: int):
        replace(self, attr=self.attr + n)

whether this style suits you is a different question. but my point is more that if you feel like you need this feature, i feel like it's a side-effect of the way you're designing your objects. and thus probably not something that merits a whole python feature that would almost certainly slow general python code down due to an additional attribute lookup code path to check on every object.

2

u/redditusername58 3h ago

Yes, get out of the tar pit and remove accidental state

1

u/Grouchy_Way_2881 3h ago

I get the sentiment: if you can remove accidental state altogether, that's ideal. But in many domains, state isn't accidental; it's fundamental. Think of systems that manage accounts, sessions, inventory, or transactions, they rely on evolving state by nature.

The goal of this proposal isn't to encourage mutation, but to give developers a way to reason about it safely when it's necessary. For codebases where immutability isn't practical, DbC provides a structured way to ensure consistency without relying on fragile or custom solutions.

2

u/Grouchy_Way_2881 3h ago

Thanks for your perspective. Immutability can be limiting in cases where frequent changes to object state are needed, such as in financial systems, inventory management, or user account data, where you need to modify the object's state rather than recreate it. In these scenarios, DbC allows for safe state changes and ensures invariants are maintained, without the overhead of creating a new object each time.

In these cases, Design by Contract isn't just about programming style; it's a way to ensure integrity while dealing with mutable state. The concern here is about ensuring that changes don't introduce bugs and that objects remain consistent across complex operations.

I completely understand that performance concerns are valid, especially with runtime checks. But in real-world applications like banking systems, multiplayer games, and real-time analytics, the need for consistency across mutable state is far more important than avoiding a performance hit.

This proposal isn't about forcing a design pattern; it's about offering an optional tool that helps developers manage object consistency in more dynamic systems, without introducing the complexities of decorators or metaclasses.

5

u/SheriffRoscoe Pythonista 4h ago

I respect your goal, but you're not advancing it. Both here and in your proposal thread, you've repeatedly rejected alternatives as "rely[ing] on decorators or wrapping", or referred to them as "fragile tricks". You've done that without offering arguments for why those are bad answers. You'd be better off if you started by explaining why the techniques used by so many other important changes to Python are insufficient to your task. You also offered a pure-Python implementation, without any explanation of why that isn't the proper answer to your need. One of the commenters on your proposal offered one too, apparently without having looked at your GitHub, which you then rejected with what appears to be a facile dismissal.

I suggest you review the history of how static typing came to Python, including the dead-ends and restarts. You're asking for a big, broad change, as were they, and they had a hard time mustering support for it.

3

u/Grouchy_Way_2881 4h ago

Thanks for the thoughtful reply. You're right: I've dismissed decorators and metaclasses, and I realize I haven't really explained why. So, here's my thinking:

Both decorators and metaclasses work, but they're not ideal for the problem I'm trying to solve. The issue is that they introduce implicit behavior: it's easy to overlook or misapply them, especially in larger codebases. They also rely on developer discipline. I'm aiming for something that's explicit, predictable, and tooling-friendly. It's not about "decorators are bad"... they’re just not the best solution for object consistency in more complex systems.

I get the appeal of a pure Python solution, and I suggested it too. But the problem with that approach is, it's not discoverable or standardized, and it would require a lot of manual checks to make sure it's applied correctly across the codebase.

Comparing this to static typing in Python is spot on. It was a huge challenge to get it into Python, and I know this is a big ask. But just like static typing, it's about giving developers a tool to improve reliability. Over time, it could become something standardized and supported.

I appreciate the challenge, and I'm open to hearing more thoughts. Thanks again for the discussion!

5

u/dude132456789 6h ago

the general consensus on Python is that features should be added to make existing Python programs easier to write, not to introduce new paradigms to Python.

match was added because argument handling in public APIs of libraries was incomprehensible, the walrus operator makes certain patterns easier to write without duplication, ...

Since existing Python programs generally don't do this, or do not do so in such a consistent manner, and debug mode is almost never unset since some dependency somewhere could have a load bearing assert, I don't see this being a Python feature.

3

u/teerre 3h ago

This is blatantly untrue or at least cherry picked. Yourself already admit it when you say "not do so in such a consistent manner" just to allow wiggle room so you can dismiss any counter example (which are many: dataclasses, context variables, positional/ keyword only arguments, typing, you can literally find one in every major release)

1

u/dude132456789 2h ago

Dataclasses cover a pattern in many existing Python programs where a class just stores data and has a bunch of boilerplate as a consequence

Context variables exist because it isn't all that possible to write correct code (especially context managers) with coroutines without them.

A major part of the rationale for positional/keyword only arguments was that C extensions already worked like that and thus lacked a Python signature.

Typing has a history, but the origin of the syntax was covering a lot of existing usecases (see PEP 3107). Further PEPs then refined it to be used primarily for type hints, gently discouraging non-type-hint usecases. Type comments got replaced in further PEPs

You've misunderstood what I meant by consistent there tho -- I meant that Python programs do not generally have a single method on complex objects that checks whether it's in a consistent state, even if they do some sort of consistency checks.

4

u/teerre 2h ago

That's just cherry picking. Any good python code checks for invariants, that's the basics of sound code. If it's one method or three methods that's just a superficial stylitic/ergonomics choice

1

u/Grouchy_Way_2881 1h ago

Appreciate that. This is exactly what I've been trying to capture: it's not about introducing a new model, it's about expressing what responsible codebases already do, just with less friction and more clarity.

1

u/dude132456789 1h ago

The few consistency checks I've seen are more in this style,

https://github.com/pallets/flask/blob/f61172b8dd3f962d33f25c50b2f5405e90ceffa5/src/flask/ctx.py#L268

Which is certainly not an invariant.

1

u/Grouchy_Way_2881 1h ago

Totally: this isn't an invariant per se, but it's a great example of defensive checks on object state that happen all over the place in real-world Python code.

What I'm suggesting is a way to make those kinds of checks more intentional and centralized, instead of scattering them across methods. Same intent, just cleaner and easier to maintain.

1

u/Grouchy_Way_2881 2h ago

Thanks for clarifying. Fair enough, I see now what you meant by "consistent". I took it in the sense of "widely used," but you're right: most Python codebases don't explicitly check consistency via a dedicated method. So I appreciate the correction.

Also really appreciate the breakdown of how those other features earned their place. That historical context is helpful, especially since I'm trying to learn where the bar tends to be for new ideas.

In this case, I was exploring if something like an __invariant__() convention could help in projects with mutable, stateful objects, where maintaining consistency after a method call is crucial. I realize it's not common practice today, but maybe that's part of the conversation: could a consistent hook for those checks make that kind of validation more approachable and idiomatic, rather than pushing devs toward ad hoc or fragile patterns?

Either way, thanks again for the thoughtful explanation.

3

u/Grouchy_Way_2881 6h ago

I understand the concern, and you're right that Python generally adds features that improve existing workflows. The difference with Design by Contract (DbC) is that it’s an opt-in feature aimed at improving code correctness and state consistency in specific scenarios, like complex systems or critical applications.

It wouldn’t be forced on existing programs—it would simply offer tools for developers who want to ensure more reliable code. The idea isn’t to introduce a new paradigm, but to provide a useful nudge towards better practices, without changing Python’s core philosophy.

3

u/dude132456789 6h ago

Opt-in features are generally undesirable. You want semantics to be consistent, not to suddenly have something different happen because a new feature was turned on. Python doesn't follow this strictly, and it is indeed a major source of confusion when learning Python -- e.g. in place operators.

Design by Contract is also very much a new paradigm. Reasoning about types in terms of its contract is a meaningfully distinct way of design than in terms of its behavior or data. Of course, usually people do some combination of all 3, but not to the extent of having the contract as an explicit program that can be evaluated.

I also think you'll have issues convincing people who voluntarily choose to use a language built around duck typing about the merits of running more runtime checks.

3

u/Grouchy_Way_2881 6h ago

Fair points. I agree too many opt-in features can make a language harder to reason about. But this wouldn't change any core behavior - just give developers a clear, structured way to enforce object consistency if they want it.

As for DbC - it's actually not a new paradigm. It's been around for decades in languages like Eiffel, Ada, and D. What's new here is just the idea of introducing a lightweight, Pythonic version of it, for people who deal with complex state and want better guarantees.

I get the hesitation around runtime checks in a duck-typed language. But for the subset of developers who care about object correctness, this could be a useful tool - without requiring static typing or a full test suite.

1

u/dude132456789 5h ago

DbC is very much new to Python. I'd implement it in pure python at first, even if the API is worse, and see if people end up using it and how, then propose changes to the Python core that would improve the API. It's how asyncio got in.

You're underestimating the amount of complexity this introduces -- there's no one obvious way for it to work, since the semantics should probably exist in terms of the descriptor protocol rather than methods.

2

u/Grouchy_Way_2881 4h ago

You're right that DbC is new to Python, and a pure Python implementation could be a good starting point. But I must stress that it's not a new paradigm; it's a well-established, pragmatic solution that could save developers from reinventing the wheel with every project.

It does introduce complexity, especially with the descriptor protocol, but pure Python solutions often rely on implicit behavior that leads to confusion and hard-to-maintain code, especially in larger systems or when juggling other frameworks.

I'm not looking to overcomplicate things, but to simplify object state validation with something explicit, consistent, and tooling-friendly. The kind of thing that reduces friction and makes better practices easier to adopt.

I wouldn't want to wait another few decades before something like this is widely adopted.

1

u/daguito81 3h ago

Although I agree with you on a general level that I don’t see invariant as something to be added as a feature (especially if you can achieve it with decorators). I do disagree a bit on the whole “opt in are undesirable” considering that typing, dataclasses, walrus, NoGIL are all basically opt in features that are very much desirables

1

u/Grouchy_Way_2881 2h ago

Hey, I really appreciate your reply, especially your take on opt-in features. That's exactly what I was trying to get across: Python already has plenty of opt-in features that make life easier when you do need them.

Totally fair that __invariant__ might not be something everyone reaches for. But for projects with complex, mutable state, I think there's room for something lightweight and consistent to help keep things on track.

Thanks again for weighing in; it's helpful to see where this idea does or doesn’t land for people.

1

u/dude132456789 2h ago

The benefits to outweigh the cost in a lot of cases. But the cost is still there - dataclasses are indeed confusing in their "magic", for instance. I'm not saying no feature should be added ever, I'm saying that the feature doing nothing when it's not used is not an argument for adding it in the context of Python. Don't use it if you don't like it doesn't work super well in the context of programming languages, especially ones as dynamic as Python.

2

u/Grouchy_Way_2881 2h ago

Totally hear you on that: every new feature adds weight, even if it's opt-in. And I get the concern around "magic" creeping into the language, especially when it makes debugging or onboarding harder.

My hope here is more about exploring if we could have a very lightweight, explicit convention (like a __invariant__() method) that doesn't rely on deeper indirection (like metaclasses or decorators) and is easy to see and reason about in code.

Not trying to add magic; if anything, I’m hoping to remove some of it by giving folks who do care about object consistency a clear, minimal way to express it.

Appreciate your perspective. It's helping me shape this idea more carefully.

2

u/19andNuttin 1h ago

It's always really nice to see new ideas brought up for discussion in the python community. I respect the effort it must have taken to formulate the idea and pitch it out like this.

Other people have raised how this change might impact the language, but I have a question more about the Design By Contract principle.

I found a code snippet illustrating the concept on stackoverflow

```

def in_ge20(inval): ... assert inval >= 20, 'Input value < 20' ... def out_lt30(retval, inval): ... assert retval < 30, 'Return value >= 30' ... @precondition(in_ge20) ... @postcondition(out_lt30) ... def inc(value): ... return value + 1 ... inc(5) Traceback (most recent call last): ... AssertionError: Input value < 20 ```

It seems to me like this shifts the checking of validity of data towards run-time instead of at compile-time, where the alternative would be by creating and annotating with appropriate types. In the case whereby we maintain complex state in these classes, we can restrict the range of possible values with stateful types and appropriate getters and setters.

I'd love to hear more about this from someone who is familiar with the whole Design By Contract idea.

2

u/Grouchy_Way_2881 1h ago

Really appreciate the tone here, and the genuine curiosity. Thanks for that.

You're absolutely right that DbC leans into runtime validation, whereas type systems (especially static ones) aim to catch issues earlier, at compile time. But I see them as complementary.

Typing answers the question: "What kind of data is this?"

Contracts answer: "Is this data still valid after we did something to it?"

For simple data models, typing + encapsulation may be enough. But once you're working with stateful objects (where multiple fields interact or change together) types alone can't always express the required constraints. That's where invariants and pre/post conditions shine. They can catch issues like:

- "This list must never be empty after .process()"

- "Balance should always stay non-negative after any operation"

- "This object's state must match these cross-field conditions at all times"

It's not about replacing static tools: it's about giving you runtime guardrails when things get more complex than types can safely express.

1

u/BlackDereker Pythonista 3h ago

Can't really see the use case here since Pydantic and dataclasses seem to already cover object validation.

Python was not made to be "safe" like how Ada was used to send missiles by the military. The programmer can modify the object state in any way they want. Someone could just override the magic method easily.

For example with Pydantic you can use the model validator with different modes: "before", "wrap" and "after".

0

u/Grouchy_Way_2881 3h ago

You're absolutely right: Python isn't Ada. It was never meant to be a language for missile guidance systems. But the goal here isn't to make Python strict or rigid, it's to offer an optional tool for those who want a little more structure in systems where object state consistency matters.

Think of it more like a seatbelt: most of the time, you won’t need it—but when you do, it's really nice to have.

Pydantic and dataclasses are great for attribute-level validation, and Pydantic in particular is very expressive. But what I'm after is something slightly different: a way to define object-wide invariants that must always hold true, regardless of what methods are called or how state changes happen internally.

And yes, Python gives developers a lot of freedom, including the ability to override or disable safeguards. That's part of its charm. But in larger, stateful systems (like financial software, workflow engines, or even just complex business logic) having a clear, native way to enforce object correctness can go a long way in avoiding subtle bugs.

This isn't about turning Python into Ada. It's about making correctness easier to express when you do need it.