I have a function that is returning either an class instance
or None
depending
on some logic.
In some places of the code I know this function is for sure not returning None
,
but mypy complains.
I made a minimal example that reproduces the situation described above.
I would like to avoid marking a_string
as a_string: Optional[str] = ""
, I
know I can also overcome the problem using cast
or type ignore
, but somehow I
feel there might be a better way.
Any recommendations how to handle this situation?
For this example I am using mypy 0.641
and python 3.7
"""
Function returns either an object or none
"""
from typing import Optional, cast
RET_NONE = False
def minimal_example() -> Optional[str]:
if RET_NONE:
return None
else:
return "my string"
a_string = ""
maybe_string = minimal_example()
a_string = maybe_string
# mypy doesn't complain if I do the following
a_string = cast(str, maybe_string)
a_string = maybe_string # type: ignore
Mypy complains as follows:
❯❯❯ mypy mypy_none_or_object.py (chatsalot) ✘ 1
mypy_none_or_object.py:19: error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str")
You can use a special comment # type: ignore[code, ...] to only ignore errors with a specific error code (or codes) on a particular line. This can be used even if you have not configured mypy to show error codes. Currently it's only possible to disable arbitrary error codes on individual lines using this comment.
“Mypy is an optional static type checker for Python that aims to combine the benefits of dynamic (or 'duck') typing and static typing. Mypy combines the expressive power and convenience of Python with a powerful type system and compile-time type checking.” A little background on the Mypy project.
Mypy is designed to treat function signatures as the "source of truth". If you indicate that some function returns an Optional[str]
, then mypy will assume that will always be the case. It won't attempt to see how any global variables may or may not alter that function signature.
The easiest possible way of working around this is to add an assert
or isinstance
check:
maybe_string = minimal_example()
reveal_type(maybe_string) # Revealed type is Optional[str]
assert maybe_string is not None # Or use 'if isinstance(maybe_string, str)
reveal_type(maybe_string) # Revealed type is str
(If you're not aware, mypy will special-case the reveal_type(...)
function: whenever mypy encounters it, mypy prints out the type of whatever expression you provide. This is useful for debugging, but you should remember to delete the pseudo-function after you're done since it doesn't exist at runtime.)
Alternatively, you could redesign your code so that your function's return value is more normalized -- it always returns a string instead of sometimes returning one.
If RET_NONE
is meant to be a more-or-less immutable global (e.g. something like "enable debug mode" or "assume we're running on Windows"), you could use is to take advantage of mypy's --always-true
and --always-false
flags and provide two different definitions of minimal_example
. For example:
RET_NONE = False
if RET_NONE:
def minimal_example() -> None:
return None
else:
def minimal_example() -> str:
return str
You then invoke mypy using mypy --always-true RET_NONE
or mypy --always-false RET_NONE
to match how your variable is defined. You can find more info about these types here and maybe here.
A fourth alternative you could explore is using function overloads: https://mypy.readthedocs.io/en/latest/more_types.html#function-overloading
However, idk if that really works in your case: you can't define overloads where only the return type differs: the argument arity or types of each overload need to be distinguishable from each other in some way.
Both solutions: cast()
and # type: ignore
are effectively turning off mypy checks for the variable. This can shadow bugs and should be avoided when possible.
In your case mypy cannot know the value of RET_NONE
, since it can be changed in runtime from False
to anything else, thus the error.
I suggest adding an assertion:
a_string = ""
maybe_string = minimal_example()
assert maybe_string is not None # <- here
a_string = maybe_string
Now mypy is sure that on the next line maybe_string
definitely won't be None
. I covered this in the Constraining types section of my blog post about typing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With