In Rust and Golang, there is this popular pattern called the new type pattern where we can define types that are a subtype of the supertype and implement methods on the subtype that constrains the value of the subtype itself by holding certain invariances.
For example, we can make a new type FourCharsStr
like so:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import NoReturn, Union
class FourCharsStr(str):
@classmethod
def _validate_str(cls, value: "str") -> "Union[str, NoReturn]":
assert len(value) == 4, f"`FourCharsStr` must have a length of 4, it has a length of {len(value)}"
return value
def __new__(cls, value: "str") -> "FourCharsStr":
new_str = super().__new__(cls, cls._validate_str(value))
return new_str
It works as follows:
try:
FourCharsStr("hello")
except AssertionError as err:
print(err) # `FourCharsStr` must have a length of 4, it has a length of 5
four_chars_str = FourCharsStr("heyu")
assert type(four_chars_str + 'o') == str # may not be ideal,
# we may want to operate on `four_chars_str` like a regular `str`
# but would want to prevent any operations that violate its invariance,
# e.g. it must continue to be a string of only 4 characters.
This is not ideal in some use cases because perhaps we want to retain the behavior of the supertype and any operations of the supertype on the subtype should either raise an Exception
if it violates the invariance of the subtype or return a (unfortunately) fresh instance of the subtype.
I made this library called python-newtypes so that one can easily create subtypes that behave like its supertype and raises exceptions if any operations on the subtype violate its invariances. However, this does not work on complex types like pandas’ DataFrame or numpy’s NDArray.
Github Repo: GitHub - jymchng/python-newtype: Extending your Python types using the `NewType` pattern easily!
Do comment on its implementation, looking forward to suggestions on its improvements.