Mypy: Smarter unwrapping for `Optional` type possible?

Created on 14 Nov 2017  路  8Comments  路  Source: python/mypy

Hi,
First of all: Thank you all for this great tool!

I am currently using mypy 0.540 and came across an inconvenience when using the Optional type.
mypy seems smart enough to detect that after checking an optional value for None the Optional part of the type is disregarded. Meaning: declaring value: Optional[int] would result in value behaving like an int after if value is not None.

So far, so good. If that value is not None check is saved into a variable before, the check fails and mypy complains about Optional[int] not being compatible with int.

I attached a small demo script for this scenario:

from typing import Optional

def strict_function(param: int) -> None:
    print(param)

value: Optional[int] = None

has_value = value is not None
if has_value:
    reveal_type(value)  # Revealed type is 'Union[builtins.int, builtins.None]'
    strict_function(value)  # causes error

if value is not None:
    reveal_type(value)  # Revealed type is 'builtins.int'
    strict_function(value)  # doesn't cause error

After invoking mypy --strict-optional test.py I get:

test.py:10: error: Argument 1 to "strict_function" has incompatible type "Optional[int]"; expected "int"

I admit, I sometimes save those checks in boolean variables for better code readability as well as avoiding repetitive checks.

It would be great if mypy could also detect these scenarios. I'm not expecting more complex checks to work, but simple ones like in this demo script would be great.

feature needs discussion priority-2-low

Most helpful comment

I know this is old but for anyone stuck on this, the only way around this I could find is to redefine the variable after the null check.

x: Optional[int] = maybe()
if x is None:
    raise Exception()

actual_x: int = x

If anyone has a better solution, let me know

All 8 comments

This is occasionally requested (there's probably already an issue) but it's a much harder thing to implement, so it probably won't happen any time soon.

I know this is old but for anyone stuck on this, the only way around this I could find is to redefine the variable after the null check.

x: Optional[int] = maybe()
if x is None:
    raise Exception()

actual_x: int = x

If anyone has a better solution, let me know

I've been doing the same thing, this may be the best you can hope for in python. mypy will still check the unwrapped type, but this depends on erasure. I have a interest in improving this as well.

assert can be useful for this:

x: Optional[int] = maybe()
assert x is not None
actual_x: int = x

assert can be useful for this:

x: Optional[int] = maybe()
assert x is not None
actual_x: int = x

Careful though, https://docs.python.org/3/reference/simple_stmts.html#assert

Assert will not always be available in a production environment. In a test environment, like pytest, using assert is fine.

I know that asserts can be disabled, and even with disabled asserts, trying to use None will likely cause an exception anyway. However, the assert makes mypy accept the code.

Are you all familiar with this library? https://github.com/dry-python/returns

x: Optional[int] = maybe()
if x is None:
    raise Exception()

actual_x: int = x

Thanks! It didn't work for me with calling maybe() directly in the if statement but additionally declaring the variable works.

Was this page helpful?
0 / 5 - 0 ratings