Until you find a piece of code that does more than what you sort-of thought it kind-of did.
def null_aware_func(x): if x is None: return x return 2.2*x**1.05
This is a stab at a none-aware computation.
Let's add type hints, shall we?
def null_aware_func(x: float) -> float: if x is None: return None return 2.2*x**1.05
This won't fool mypy. Sigh. It passes unit tests, but it's flagged as a problem.
We have a variety of ways of define this function. And that means we need to think carefully about our None-aware design.
Is this really an @overload?
from typing import overload @overload def null_aware_func(x: None) -> None: ... def null_aware_func(x: float) -> float: if x is None: return None return 2.2*x**1.05
And yes, the ... is legit Python syntax. (It's a rarely used token that forms the body of the function.)
Or is this a more advanced type?
from typing import Optional OptFloat = Optional[float] def null_aware_func(x: OptFloat) -> OptFloat: if x is None: return None return 2.2*x**1.05
I'd argue that OptFloat is a more sensible definition. However, if this is the only function that's none-aware, perhaps it's an overload.
The deeper question is one of underlying meaning. Why are we doing this? What does it mean?
And. Bonus. Will this be working in a SQLAlchemy environment, where they have their own wrappers for database objects, meaning that `is None` doesn't work and `== None` is required?
What's important is that adding type hints forced us to think about what we were doing. Unlike Java we did this without stopping progress for an extended period of "wrestling with the compiler". We can use Any temporarily because the unit tests all pass. Then, we can pay down the technical debt by fixing the type declaration.