Skip to content

mypy fails to narrow Union[float,int] to float after isinstance(x, float) #6060

Description

@DarwinAwardWinner

Mypy fails to typecheck the following valid code:

from typing import Union
def maybe_convert_to_int(x: Union[float,int]) -> Union[float,int]:
    '''Convert x from float to int if it can be done losslessly.'''
    if isinstance(x, float):
        # x must be a float here, so this is a legal operation, but
        # mypy still thinks its type is Union[float,int]
        assert isinstance(x, float)
        if x.is_integer():
            x = int(x)
    return x

When I run mypy on it, I get:

$ mypy ~/temp/test_mypy.py
temp/test_mypy.py:8: error: Item "int" of "Union[float, int]" has no attribute "is_integer"
$ python --version
Python 3.7.1
$ mypy --version
mypy 0.650

I would expect this program to pass the type checker, since I am using isinstance to narrow the type to float before performing the float-specific operation float.is_integer. If I replace the assertion with x = float(x), that satisfies the type checker.

Activity

  1. ilevkivskyi commented on Dec 12, 2018

    @ilevkivskyi
    Member

    This is because some special-casing in mypy called "type promotions". The point is that int is not a subclass of float at runtime, but mypy considers int a subtype of float. Because of this, int sneaks into the if statement, where mypy complains about the missing method.

    We try to ignore the type promotions while working with isinstance(), but it looks like there is a bug and type promotions are still used. Another possible workaround (apart from the one you mentioned) for you would be to just annotate the functions as (float) -> float. You can probably also change the logic to

    def maybe_convert_to_int(x: float) -> float:
        if isinstance(x, int):
            return x
        if x.is_integer():
            x = int(x)
        return x
  2. ilevkivskyi commented on Dec 12, 2018

    @ilevkivskyi
    Member

    @Michael0x2a was among the last who worked with this code, so probably he has the most context.

  3. DarwinAwardWinner commented on Dec 12, 2018

    @DarwinAwardWinner
    Author

    I discovered a workaround that eliminated this code entirely for my specific use case. The reason for this code was to ensure that a float like 1.0 would print as an integer (with no decimal point), but I figured out that '{:g}'.format(x) does that without having to check the type of x.

    However, I guess this is still a bug in the general case.

  4. self-assigned this
    on Dec 12, 2018
  5. GregHilston commented on Aug 26, 2019

    @GregHilston

    I believe I'm still experiencing this issue:

    processed_corpus: Dict[str, Dict[str, Union[List[Dict[Any, Any]], float]]] = {}
    
    # bunch of code removed
    
    if sentence not in processed_corpus:
        processed_corpus[sentence] = {"extra": extra}
     # satisfying mypy due to Union in type hinting
    elif sentence in processed_corpus and isinstance(processed_corpus[sentence]["extra"], list):
        processed_corpus[sentence]["extra"].extend(extra)
    

    Still getting a mypy error of

    error: Item "float" of "Union[List[Dict[Any, Any]], float]" has no attribute "extend"

  6. syastrov commented on Nov 14, 2019

    @syastrov
    Contributor

    I can also reproduce this, or a similar issue having to do with statement reachability, both on mypy 0.740 and master (mypy 0.750+dev.e97377c454a1d5c019e9c56871d5f229db6b47b2)

    from typing import Union
    def get_number_type(v: Union[float, int]) -> str:
        if isinstance(v, float):
            return "float"
        if isinstance(v, int):
            return "int"  # Error: Statement is unreachable

    Changing the order of the isinstance checks makes the error go away.

  7. added a commit that references this issue on Oct 3, 2022
    08c566c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions