fonini
fonini

Reputation: 3389

How to narrow a list of unions?

I have a variable of type list[A | B] that could hold a mixed list (like [A(), B(), A()]).

If I later reach some corner case and I want to make sure all elements are actually of type A, I can assert isinstance inside a for-loop:

def f(mylist: list[A | B]):
    ...
    ...
    for el in mylist:
        assert isinstance(el, A)
    # Now, I'm sure that mylist is actually `list[A]`
    # How do I tell that to the type checker?

After the for-loop, if I reveal_type(mylist) it still says list[A|B]. I also tried assert all(isinstance(el, A) for el in mylist) instead of the explicit loop, but mypy still isn't able to narrow it. Is this possible? Or do I have to use cast here?

Upvotes: 3

Views: 773

Answers (1)

bad_coder
bad_coder

Reputation: 12890

Or do I have to use cast here?

Using Python 3.10 the only alternative to using cast that can narrow a list would be using a TypeGuard, this code:

from typing import TypeGuard


def is_str_list(val: list[int | str]) -> TypeGuard[list[str]]:
    """Determines whether all objects in the list are strings"""
    return all(isinstance(x, str) for x in val)


def is_int_list(val: list[int | str]) -> TypeGuard[list[int]]:
    """Determines whether all objects in the list are ints"""
    return all(isinstance(x, int) for x in val)


def f(mylist: list[int | str]):

    if is_str_list(mylist):
        reveal_type(mylist)

    elif is_int_list(mylist):
        reveal_type(mylist)

    reveal_type(mylist)

narrows the type of the list without using cast:

narrow_collection.py:17: note: Revealed type is "builtins.list[builtins.str]"
narrow_collection.py:21: note: Revealed type is "builtins.list[builtins.int]"
narrow_collection.py:24: note: Revealed type is "builtins.list[Union[builtins.int, builtins.str]]"

Upvotes: 4

Related Questions