python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.33k stars 2.81k forks source link

Checking elements of a `list[str | None]` after discarding the possibility of `None` #16243

Open DaNthEB0T opened 1 year ago

DaNthEB0T commented 1 year ago

Bug Report

str_none: list[str | None]
only_str: list[str]
for i in range(len(sequence)):
    if sequence[i] is not None:
        only_str.append(sequence[i]) # Here mypy raises an error due to "incompatible types"

The code above raises the following error Argument 1 to "append" of "list" has incompatible type "str | None"; expected "str".

I've found the following verbose workaround but to my taste its :

str_none: list[str | None]
only_str: list[str]
for i in range(len(sequence)):
    element = sequence[i]
    if element is not None:
        only_str.append(element)

To Reproduce

See code example above.

Expected Behavior

I'm expecting mypy to be able to predict future annotations of sequences with multiple types possible after discarding a specific type in a condition.

Actual Behavior

All is included above.

Your Environment

erictraut commented 1 year ago

A static type checker must be very careful about assuming that sequence[i] in one location will produce the same type as sequence[i] when used a second time in another part of the function, especially when loops are involved. For this reason, mypy doesn't apply type narrowing to index expressions with dynamic subscripts.

You can use the walrus operator to avoid accessing sequence[i] multiple times.

def func(sequence: list[str | None], only_str: list[str]):
    for i in range(len(sequence)):
        if (x := sequence[i]) is not None:
            only_str.append(x)

If you're looking for something that's less verbose, you could also use a comprehension.

def func(sequence: list[str | None], only_str: list[str]):
    only_str += (x for x in sequence if x is not None)