Search code examples
pythonmypytyping

mypy: Incompatible types in assignment when overwriting a variable


I'm getting an assignment error when running mypy version 0.942 on my script that I can't make sense of.

I have a variable price_point that takes the form of a string, and depending on if the string contains a "-", will determine if the variable is manipulated into a list before being passed to another function _other_func(). The variable can also be None.

Attempt 1:

The code:

def _other_func(
    price_points: Optional[Union[List[str], str]]
) -> float:
    return 1.0

def get_rate(price_point: Optional[str])->float:
    """
    Args:
        price_point: String representing single price or range of prices
        e.g. '100' or '100-200'. Can also be None.
    """

    if price_point and len(price_point_range := price_point.split("-")) > 1:

        print(f"Checking range {price_point_range[0]} to {price_point_range[1]}")
        price_point = price_point_range

    return _other_func(price_point)

Error returned:

error: Incompatible types in assignment (expression has type "List[str]", variable has type "Optional[str]")  [assignment]
            price_point = price_point_range
                          ^

Can someone please explain why mypy is concerned about this variable, since the List type is accounted for in the Union of types in the _other_func() input? I'd be open to hearing alternate suggetions.


Attempt 2:

I thought the issue might be to do with the name of the variable, so I tried this alternative to rename the variable before passing it to _other_func():

def get_rate(price_point: Optional[str])->float:
    """
    Args:
        price_point: String representing single price or range of prices
        e.g. '100' or '100-200'. Can also be None.
    """

    if price_point and len(price_point_range := price_point.split("-")) > 1:

        print(f"Checking range {price_point_range[0]} to {price_point_range[1]}")
        price_point_input = price_point_range
    else:
        price_point_input = price_point

    return _other_func(price_point_input)

But the same error persists:

error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "List[str]")  [assignment]
            price_point_input = price_point
                                ^

I'm not sure if I'm just having one of those days of staring at the screen for too long...


Solution

  • Static types cannot be changed at runtime. price_point is a Optional[str], and it must stay that way for the duration of the call to get_rate. If you want to pass a value of a different type to other_func, you need to use a different variable that can hold either a string or a list (or None):

    def get_rate(price_point: Optional[str])->float:
        """
        Args:
            price_point: String representing single price or range of prices
            e.g. '100' or '100-200'. Can also be None.
        """
       
        arg: Optional[Union[list[str], str]] = price_point
    
        if price_point and len(price_point_range := price_point.split("-")) > 1:
    
            print(f"Checking range {price_point_range[0]} to {price_point_range[1]}")
            arg = price_point_range
    
        return _other_func(arg)
    

    However, I would probably skip the new variable, and just make two calls to _other_func, each with the appropriate argument.

    def get_rate(price_point: Optional[str])->float:
        """
        Args:
            price_point: String representing single price or range of prices
            e.g. '100' or '100-200'. Can also be None.
        """
    
        
        if price_point and len(price_point_range := price_point.split("-")) > 1:
    
            print(f"Checking range {price_point_range[0]} to {price_point_range[1]}")
            return _other_func(price_point_range)
    
        return _other_func(price_point)
    

    Even better, consider "unifying" the three possibilities under the single type list[str], which should hold 0, 1, or 2 strings.

    def _other_func(
        price_points: list[str]
    ) -> float:
        match len(price_points):
            case 0:
                ...  # Old None case
            case 1:
                ...  # Old str case
            case 2:
                ...  # Old list[str] case
            case _:
                raise ValueError("Too many items in the list")
    
    
    def get_rate(price_point: Optional[str])->float:
        arg: list[str]
        if not price_point:
            arg = []
        elif len(price_point_range := price_point.split("-")) > 1:
            print(f"Checking range {price_point_range[0]} to {price_point_range[1]}")
            arg = price_point_range
        else:
            arg = [price_point]
    
        return _other_func(arg)