Trying to avoid typing issues I often run into the same problem.
E.g. I have a function x
that very rarily returns value None
, all other times it returns int
.
def x(i: int) -> Union[int, None]:
if i == 0:
return
return i
def test(i: int):
a = x(i)
# typing issue: *= not supported for types int | None and int
a *= 25
x
used very often in the codebase and most of the time i
was already checked a hundred times that x(i)
will indeed return int
and not None
.
Using it as int
right away creates typing warnings - e.g. you can't multiply possible None
value.
What's best practice for that case?
Ideas I considered:
None
with if a is None: return
as it's already known.a *= 25 # type: ignore
will make a
an Unknown
type.a = x(i) # type: int
will make the warning go away. But will create a new warning "int | None cannot be assigned to int"a = cast(int, x(i))
, haven't tested it much yet.I usually end up changing return type of x
to just int
, adding ignore
in return # type: ignore
and mention in the docstring that it can return None
, it helps avoiding contaminating the entire codebase with type warnings. Is this the best approach?
def x(i: int) -> int:
"""might also return `None`"""
if i == 0:
return # type: ignore
return i
This might be a case where an exception is better than a return statement you never expect to be reached.
def x(i: int) -> int:
if i == 0:
raise ValueError("didn't expect i==0")
return i
def test(i: int):
try:
a = x(i)
except ValueError:
pass
a *= 25
Code that is confident it has sufficiently validated the argument to x
can omit the try
statement.
Statically speaking, this is accurate: if x
returns, it is guaranteed to return an int
. (Whether it will return is another question.)
Ideally, you could define a refinement type like NonZeroInt
, and turn i == 0
into a type error, rather than a value error.
# Made-up special form RefinementType obeys
#
# isinstance(x, RefinementType[T, p]) == isinstance(x, T) and p(x)
NonZeroInt = RefinementType[int, lambda x: x != 0]
def x(i: NonZeroInt) -> int:
return i
x(0) # error: Argument 1 to "x" has incompatible type "int"; expected "NonZeroInt" [arg-type]
i: int = 0
x(i) # same error
j: NonZeroInt = 0 # error: Incompatible types in assignment (expression has type "int", variable has type "NonZeroInt") [assignment]
x(j) # OK
k: NonZeroInt = 3 # OK
x(k) # OK