Search code examples
pythonpython-typingmypy

MyPy - "Incompatible types in assignment (expression has type None, variable has type ...)"


I've got the following function, which given a string of the form 'a-02/b-03/foobarbaz_c-04', will extract the digits after a, b and c. The issue is that, for my use case, the input strings may not contain c, such that there will be no digits to extract.

Here's the code:

from typing import Tuple, Optional


def regex_a_b_c(name: str) -> Tuple[int, int, Optional[int]]:
        a_b_info = re.search('a-(\d\d)/b-(\d\d)/', name)
        a, b = [int(a_b_info.group(x)) for x in range(1, 3)]
        c_info = re.search('c-(\d\d)', name)
        if c_info:
            c = int(c_info.group(1))
        else:
            c = None   
        return a, b, c

The issue I have is that, despite trying to make it clear that the last return argument is an Optional[int], I can't get my linter to stop complaining about the variable c.

I get a warning at the line c = None that says:

Incompatible types in assignment (expression has type None, variable has type int)

How can I solve the issue?


Solution

  • If you don't annotate a variable, mypy will infer its type based on the very first assignment it sees.

    So in this case, the line c = int(_info.group(1)) appears first, so mypy decides that the type must be int. It then subsequently complains when it sees c = None.

    One way of working around this limitation is to just forward-declare the variable with the expected type. If you are using Python 3.6+ and can use variable annotations, you can do so like this:

    c: Optional[int]
    if c_info:
        c = int(c_info.group(1))
    else:
        c = None
    

    Or perhaps more concisely, like this:

    c: Optional[int] = None
    if c_info:
        c = int(c_info.group(1))
    

    If you need to support older versions of Python, you can annotate the type using the comment-based syntax, like so:

    c = None  # type: Optional[int]
    if c_info:
        c = int(c_info.group(1))
    

    rje's suggestion of doing:

    if c_info:
        c = int(c_info.group(1))
        return a, b, c
    else:
        return a, b, None
    

    ...is also a reasonable one.