How to handle Unsupported operand types in mypy when type should be clear?
Question:
Consider the following toy example.
class MyClass:
def __init__(self, optional_arg: float | None = None) -> None:
self.optional_arg = optional_arg
self._parse_args()
def _parse_args(self) -> None:
if self.optional_arg is None:
self.optional_arg = 0.5
def multiply(self) -> float:
# mypy is complaining with the below statement:
# error: Unsupported operand types for * ("float" and "None") [operator]
# Right operand is of type "Optional[float]"
return 0.5 * self.optional_arg
After instantiating this class with optional_arg=None
, it should be clear that self.optional_arg
will be set to a float
due to the _parse_args
method.
In my view, it should be clear that when calling the method multiply
, it will return a float
. However, mypy
is still complaining that self.optional_arg
might be None
.
What is a pythonic way to tell mypy
that self.optional_arg
can’t possibly be None
?
Answers:
self.optional_arg
doesn’t have to be the value set by _parse_args
. Someone might do
x = MyClass()
x.optional_arg = None
x.multiply()
and as far as mypy is concerned, that’s a valid way to use MyClass
.
Alternatively, someone might override _parse_args
:
class Subclass(MyClass):
def _parse_args(self) -> None:
pass
Subclass().multiply()
and as far as mypy is concerned, that’s legal too.
If you want mypy to treat self.optional_arg
as never None
, you shouldn’t set it to a value that could be None
in the first place. You can use a float as the default in the first place:
class MyClass:
optional_arg: float
def __init__(self, optional_arg: float = 0.5) -> None:
self.optional_arg = optional_arg
...
or do your None
handling before you set the attribute:
class MyClass:
optional_arg: float
def __init__(self, optional_arg: float | None = None) -> None:
self.optional_arg = optional_arg if optional_arg is not None else 0.5
...
user2357112 answer is great and you should go for it in that case.
However not everytime it’s easy to have proper typing (excecially working with legacy code). In such case you can use type narrowing.
def multiply(self) -> float:
assert isinstance(self.optional_arg, float)
return 0.5 * self.optional_arg
Consider the following toy example.
class MyClass:
def __init__(self, optional_arg: float | None = None) -> None:
self.optional_arg = optional_arg
self._parse_args()
def _parse_args(self) -> None:
if self.optional_arg is None:
self.optional_arg = 0.5
def multiply(self) -> float:
# mypy is complaining with the below statement:
# error: Unsupported operand types for * ("float" and "None") [operator]
# Right operand is of type "Optional[float]"
return 0.5 * self.optional_arg
After instantiating this class with optional_arg=None
, it should be clear that self.optional_arg
will be set to a float
due to the _parse_args
method.
In my view, it should be clear that when calling the method multiply
, it will return a float
. However, mypy
is still complaining that self.optional_arg
might be None
.
What is a pythonic way to tell mypy
that self.optional_arg
can’t possibly be None
?
self.optional_arg
doesn’t have to be the value set by _parse_args
. Someone might do
x = MyClass()
x.optional_arg = None
x.multiply()
and as far as mypy is concerned, that’s a valid way to use MyClass
.
Alternatively, someone might override _parse_args
:
class Subclass(MyClass):
def _parse_args(self) -> None:
pass
Subclass().multiply()
and as far as mypy is concerned, that’s legal too.
If you want mypy to treat self.optional_arg
as never None
, you shouldn’t set it to a value that could be None
in the first place. You can use a float as the default in the first place:
class MyClass:
optional_arg: float
def __init__(self, optional_arg: float = 0.5) -> None:
self.optional_arg = optional_arg
...
or do your None
handling before you set the attribute:
class MyClass:
optional_arg: float
def __init__(self, optional_arg: float | None = None) -> None:
self.optional_arg = optional_arg if optional_arg is not None else 0.5
...
user2357112 answer is great and you should go for it in that case.
However not everytime it’s easy to have proper typing (excecially working with legacy code). In such case you can use type narrowing.
def multiply(self) -> float:
assert isinstance(self.optional_arg, float)
return 0.5 * self.optional_arg