I can't seem to find an acceptable annotation for a variable that receives a value that is either: a) function returning int
or b) None
. The wrinkle is that the function returns a value that is an optional keyword parameter of the parent function, so it was previously declared as Optional[int]
. However, the runtime assignment guarantees that the function will never return None
.
If I remove the annotation, mypy
accepts it as golden. But I'd prefer to use some (acceptable) annotation. In for a penny, in for a pound...
My code:
from typing import Optional, Callable
def myfun(p1: Optional[int] = None):
# mypy complains about this
dyn_p1:Optional[Callable[[], int]] = (lambda: p1) if p1 else None
# ... but has no problem with this
# dyn_p1 = (lambda: p1) if p1 else None
otherFun(dyn_p1)
# I expect the parameter annotation here to be checked at the point of invocation above.
def otherFun(dyn_p1: Optional[Callable[[], int]]):
pass
Here's the mypy
error:
PS $ mypy .\prompt_toolkit\shortcuts\test1.py
prompt_toolkit\shortcuts\test1.py:6: error: Incompatible types in assignment (expression has type "Optional[Callable[[], Optional[int]]]", variable has type "Optional[Callable[[], int]]")
prompt_toolkit\shortcuts\test1.py:6: error: Incompatible return value type (got "Optional[int]", expected "int")
Found 2 errors in 1 file (checked 1 source file)
# comment out the first dyn_p1 and uncomment the second, run again:
PS $ mypy .\prompt_toolkit\shortcuts\test1.py
Success: no issues found in 1 source file
I (kinda) don't think the issue here is your lambda. I think it's actually
p1
.Suppose you had instead
After all, the lambda returns
p1
.p1
is an integer. The lambda returns an integer. The lambda isCallable[[], int]
. Except when it's None, and Optional makes everything happy.In your case,
p1
isn't anint
. It's anOptional[int]
. So MyPy correctly infers that it's aCallable[[], Optional[int]]
. That disagrees with the type hint, so it squawks.Now you might say "But MyPy should be able to do code flow analysis, and know that the lambda only gets asigned in the case where
p1
isn't None."Which... is true. And MyPy can do some code flow analysis. For example, it's fine with this:
I suspect the catch here is that lambdas are late bound. That is, although you've checked that your
p1
is notNone
at the point where you assign it, it's still entirely typing legal to set it back toNone
after you createdyn_p1
.If you had the code
even though
p1
starts as an int, the result would beNone
because that's whatp1
is at the point the lambda is executed.MyPy doesn't analyse control flow to ensure this variable never gets changed to something type-legal. Instead it warns you that easy-to-forget Python rules mean that might not be as predictable as it looks at first sight.
One thing you could do is introduce another variable which is annotated as
int
. Because it would not be legal to change the new variable toNone
, MyPy can stopy worrying.Rather ironically, in your example of it works if you don't check, MyPy doesn't notice the late binding problem. It's not marking it as
Any
or anything; it actually infers the type you wanted to tell it!This type checks fine. The revealed type is 'Union[def () -> builtins.int, None]' both times (which is MyPy spelling of Optional[Callable[[], int]]). But if you actually call it, it sneaks through an unauthorised
None
.