Reputation: 935
I have, probably to my shame, just started including type checking in my python code. Most of the type checking is straight forward but I'm a little lost as to the pythonic way to deal with functions that can return None
For example
threads = os.cpu_count() * 1.2
this raises the MyPy error
Mypy: Unsupported operand types for * ("None" and "float")
So to get rid of this I changed the code to
default = (os.cpu_count() if os.cpu_count() is not None else 1.0) * 1.2
But that gives exactly the same error.
What is the best way of dealing with this?
Upvotes: 2
Views: 6407
Reputation: 46823
You get the error because in the line:
default = (os.cpu_count() if os.cpu_count() is not None else 1.0) * 1.2
mypy
can't tell that the second call will be the same as the second... so it can't infer that the second call is not going to be None
. Also, it's a little bit silly to call this function twice (imagine if this function were doing a lot of work and taking a long time, it wouldn't be desirable to use the pattern you used). So there must be better ways to express that.
I believe that the cleanest and most straightforward fix is to use an auxiliary variable for the value returned by os.cpu_count()
:
nb_cpus = os.cpu_count() # type: Optional[int]
default = (nb_cpus if nb_cpus is not None else 1) * 1.2 # type: float
The following can look more Pythonic (given in a comment by hoefling):
default = (os.cpu_count() or 1) * 1.2
It's great, but be aware that it's not quite semantically equivalent to what you're doing: the value of (os.cpu_count() or 1)
will be 1
if os.cpu_count()
is 0
too. That's probably not a problem with os.cpu_count()
since it shouldn't return 0
, but keep that semantic difference in mind when you're using such “shortcuts” (shortcuts like these always look cool, but you must understand their real meaning before you use them).
Upvotes: 0
Reputation: 66
Your original code didn’t pass mypy because you have two distinct calls to the same function, and each returns a different Optional[int]
. Narrowing the type of one doesn’t affect the type of the other. Consider this extreme case:
import random
def maybe_an_int() -> Optional[int]:
if random.random() > 0.9:
return 1
return None
default = (maybe_an_int() if maybe_an_int() is not None else 1.0) * 1.2
If the first call isn’t None, we call it again. But clearly, since the return type is random, we can’t predict what the return type of the second call will be.
Many codebases have different ways of dealing with this. One option would be something like this:
from typing import TypeVar
T = TypeVar('T')
def default_optional(value: Optional[T], default: T) -> T:
if value is None:
return default
return value
Upvotes: 5