Use type hinting with bound constraints, e.g. `int[0:15]`

Proposal: Introduce a syntax extension to Python’s type hints that allows developers to specify constraints on the values of variables. This extension would enhance the expressiveness of type hints by providing a concise way to define constraints, ensuring that variables adhere to specific conditions.

Example Syntax:

x: int[1:]  # Denotes an integer variable with a constraint that it must be greater than or equal to 1.
x = 5  # OK

y: int[0:15] = -1  # this should fail!

Motivation:

This idea is driven by the principle of encapsulation, where the internal state of an object is controlled by the class itself to maintain a consistent and valid state.

  • Enhance the readability and expressiveness of type hints by incorporating value constraints directly.
  • Enable developers to communicate and enforce certain constraints on variable values at the type hint level.
  • Provide a more comprehensive type hinting system that captures both the type and constraints of variables.

Potential Benefits:

  • Improved code clarity through more expressive type hints.
  • Enforced constraints at the type hint level, potentially catching errors earlier in the development process.
  • Consistent with Python’s focus on readability and simplicity.

Considerations:

  • Balance between expressiveness and simplicity in the language.
  • Compatibility with existing type hinting tools and practices.
  • Could be partially implemented using __class_getitem__

To be more concrete, it seems like the semantics would be that x: type[a:b] means that the following statements must be true:

  • x < a is False
    x <= b is True

Which assumes that hasattr(type, __lt__).

Or would it be better for it just to be defined for integral types where every value from a to b can be enumerated?

The issue with constraints is that most of them cannot easily be statically validated, so for static type checking they’re almost only useful for immutable literal types, i.e. str/int/float, which is already partially covered by Literal, so expanding Literal with useful shorthands such as Literal[0:] or Literal[r"[0-9]+"] rather than having to spell out each individual literal would I think already give us everything we want for static analysis. But literal constraints already can be kind of annoying, since unless you directly pass in the literal as an argument, you will probably have to annotate the original variable in addition to the parameter, so I don’t know how heavily this feature would be used.

I agree that for runtime validation and for conveying intent to the consumer of the code it would be nice to have a terse way to annotate constraints, but I’m not convinced it’s worth the extra complication in the type system, since you would then probably also need a ConstraintVar and proper subclassing rules to make it work just as intuitively as generics. I think it makes more sense to keep leaning on Annotated and libraries like pydantic to define their own semantics on how to define constraints, since you could arguably make them arbitrarily complex for arbitrary types, whereas if you wanted static analysis tool to understand constraints they would either have to be extremely limited in scope or mostly ignored with a few exceptions.

2 Likes

If you want an integral value that is bounds-checked, e.g. IntEnum can do that.

Oh, you wanted to do arithmetic with the value? Then what’s your plan for when a mathematically correct computed result is out of range?

I hope you weren’t planning, e.g. that if we type-hint x and y as int[-5:10], that the type-checker should be able to infer the type of x * y as int[0:82]? (“Huh, not 100?” Well, presumably the endpoints should have the same inclusive-exclusive semantics that slicing normally does?) And of course even x += 1 is out of the question regardless.

Hopefully it’s clear how much of a mess this makes, and how quickly.

All of the above is true, of course:

  • yes, @pylead, it would be nice to succinctly declare intended range
  • yes, @jacgoldsm, that would imply a comparable type
  • yes, @Daverball, static validation is complex and cannot be expected from type checkers (perhaps from different tools, I.e. value checkers)
  • yes, @kknechtel, taking this too far too fast would make a mess

Perhaps a modest first step could be annotated-types.
It partly achieves the first objective, i.e. declare range (and other constraints) as short as currently possible. It explicitly doesn’t deal with checking those ranges.