Search code examples
pythonpython-3.xmypy

Correct typing for list of ints


Consider the following code in file x.py

from typing import List, Optional

my_list: List[Optional[int]] = list()
for i in range(7):
    my_list.append(i)
my_list = sorted(my_list)
if (len(my_list) > 0):
    my_list.append(None)
# do something with my_list

The command mypy x.py gives the following error:

x.py:6: error: Value of type variable "SupportsRichComparisonT" of "sorted" cannot be "Optional[int]"

So I changed x.py to the following:

from typing import List

my_list: List[int] = list()
for i in range(7):
    my_list.append(i)
my_list = sorted(my_list)
if (len(my_list) > 0):
    my_list.append(None)
# do something with my_list

But now mypy x.py outputs

x.py:8: error: Argument 1 to "append" of "list" has incompatible type "None"; expected "int"

So how do I annotate the code correctly for mypy to not complain (of course both versions behave exactly the same during runtime and work as expected)?

The "None"-element at the end of the list is needed in the "do something"-part of the script (which is not relevant for the problem and therefore not shown here)

EDIT (19/09/2022) START As requested, this is the productive code:

from typing import List, Optional

def parse_job_attributes(string: str, tokens: List[str]) -> List[str]:
    indices = []
    for token in tokens:
        if token in string.lower():
            indices.append(string.lower().index(token))
    sorted_indices: List[Optional[int]] = list(sorted(indices))

    # indices = sorted(indices)
    if len(sorted_indices) > 0:
        sorted_indices.append(None)
    parts = [string[sorted_indices[i]:sorted_indices[i + 1]] for i in range(len(sorted_indices) - 1)]
    return parts

EDIT END


Solution

  • You can get around the problem by looking at what your actual code is doing: None as the endpoint of the slice means len(string), so if you put that instead, then your list can stay List[int].

    from typing import List
    
    def parse_job_attributes(string: str, tokens: List[str]) -> List[str]:
        indices = []
        for token in tokens:
            if token in string.lower():
                indices.append(string.lower().index(token))
        indices.sort()
    
        if len(indices) > 0:
            indices.append(len(string))
        parts = [string[indices[i]:indices[i + 1]] for i in range(len(indices) - 1)]
        return parts
    

    This passes mypy --strict.

    Example output, just to be sure:

    >>> parse_job_attributes('re_foobar', ['foo', 're'])
    ['re_', 'foobar']
    

    As well, since you're subtracting one from the len in range(len(indices) - 1), there's not even any need to check if len(sorted_indices) > 0; you can append unconditionally, and the range will still be empty. That opens up some other solutions, like:

    sorted_indices: List[Optional[int]] = [*sorted(indices), None]
    

    P.S. Note that this whole answer is based on the "do something" part, which you thought was unimportant. I don't bring it up to shame you or anything, but to emphasize that it's important to provide working code to avoid the XY problem.