python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.57k stars 2.84k forks source link

Using numpy types conditionally gives confusing `[assignment]` error #16875

Open cebtenzzre opened 9 months ago

cebtenzzre commented 9 months ago

Bug Report

When running strict mode on a simple example with numpy types, mypy will generate a confusing error message for [assignment] in which the expression type and the variable type are apparently the same, yet it fails anyway.

To Reproduce

import random

import numpy as np

def foo() -> None:
    if random.randrange(2):
        new_dtype = np.float16
    else:
        new_dtype = np.float32

Expected Behavior

If there is something wrong with the above code, I would expect mypy to tell me what the specific mismatch is.

Actual Behavior

$ mypy --strict repr.py
repr.py:9: error: Incompatible types in assignment (expression has type
"type[floating[Any]]", variable has type "type[floating[Any]]")  [assignment]
            new_dtype = np.float32
                        ^~~~~~~~~~
Found 1 error in 1 file (checked 1 source file)

Why would type[floating[Any]] not match type[floating[Any]]?

Your Environment

BdSCunha commented 9 months ago

In NumPy, numpy.floating serves as the base class for all floating-point numbers. When a variable is annotated with np.floating, its type becomes floating[Any].

float16 and float32 are aliases for floating[_16Bit] and floating[_32Bit] respectively in NumPy. This makes them incompatible with each other due to the different bit sizes.

To address this issue, I've revised the code as follows:

import random

import numpy as np
from numpy.typing import DTypeLike

def foo() -> None:
    new_dtype: DTypeLike
    if random.randrange(2):
        new_dtype = np.float16
    else:
        new_dtype = np.float32

In this version, new_dtype is annotated with numpy.typing.DTypeLike, which is a type hint that encompasses all data types in NumPy, including np.float16 and np.float32. This change should resolve the type incompatibility issue.

cebtenzzre commented 9 months ago

float16 and float32 are aliases for floating[_16Bit] and floating[_32Bit] respectively in NumPy. This makes them incompatible with each other due to the different bit sizes.

That's a reasonable explanation, and I already had a solution for this in my code - but my concern is with the error from numpy that appears tautologically false. If mypy sees the type of np.float16 as type[floating[_16Bit]], it should communicate that to the user.

brianschubert commented 1 week ago

Reproducer without numpy:

from typing_extensions import TypeAlias

class A: ...
class B: ...

class Foo[T]: ...

FooA: TypeAlias = Foo[A]
FooB: TypeAlias = Foo[B]

def func() -> None:
    if int():
        new_dtype = FooA
    else:
        new_dtype = FooB  # E: Incompatible types in assignment (expression has type "type[Foo[Any]]", variable has type "type[Foo[Any]]")  [assignment]