1

With the code

from typing import overload
import numpy as np
import numpy.typing as npt

class BoundedArray:
    @overload
    def __init__(
        self,
        array: npt.ArrayLike,
        *,
        upper_bounds: npt.ArrayLike,
        lower_bounds: npt.ArrayLike | None,
    ) -> None:
        ...

    @overload
    def __init__(
        self,
        array: npt.ArrayLike,
        *,
        upper_bounds: npt.ArrayLike | None,
        lower_bounds: npt.ArrayLike,
    ) -> None:
        ...

    def __init__(self, array, *, upper_bounds=None, lower_bounds=0.0):
        self.array = array
        self.upper_bounds = upper_bounds
        self.lower_bounds = lower_bounds

        self.clip()

    def clip(self) -> None:
        if self.upper_bounds is None and self.lower_bounds is None:
            return

        np.clip(self.array, self.lower_bounds, self.upper_bounds, out=self.array)

I get the type-checking error

> Overloaded implementation is not consistent with signature of overload 1
>>   Type "(self: Self@BoundedArray, array: Unknown, *, upper_bounds: Unknown | None = None, lower_bounds: float = 0) -> None" cannot be assigned to type "(self: Self@BoundedArray, array: ArrayLike, *, upper_bounds: ArrayLike, lower_bounds: ArrayLike | None) -> None"
>>>     Keyword parameter "lower_bounds" of type "ArrayLike" cannot be assigned to type "float"
>>>>       Type "ArrayLike" cannot be assigned to type "float"
>>>>>         "_SupportsArray[dtype[Unknown]]" is incompatible with "float"

According to the documentation of overload the actual implementation should be ignored by the type-checker which apparently is not the case here. Taking this as a given, simply annotating the actual implementation with

    def __init__(
        self,
        array: npt.ArrayLike,
        *,
        upper_bounds: npt.ArrayLike | None,
        lower_bounds: npt.ArrayLike | None,
    ) -> None:

to rectify the falsely inferred "float" type does not seem to do the trick, as it leads to the errors

> Argument of type "ArrayLike | None" cannot be assigned to parameter "a_min" of type "None" in function "clip"
> Argument of type "ArrayLike | None" cannot be assigned to parameter "a_max" of type "ArrayLike" in function "clip"

even though I specifically try to prevent that with the type guard.

The only way I have found to not get any errors is annotating the implementation like

    def __init__(
        self,
        array,
        *,
        upper_bounds=None,
        lower_bounds: npt.ArrayLike | None = 0.0,
    ):

but it seems to be quite inconsistent to me (only annotating a single variable). Am I missing anything, is this just a buggy implementation, is there a different solution or is it supposed to work this way?

cymin
  • 83
  • 8
  • Is a `float` considered an `ArrayLike` value, or do you need to use a NumPy type like `numpy.float64`? – chepner May 26 '22 at 16:56
  • I think `float` is considered an `ArrayLike` value, since `a: float = 1.0` followed by `b: npt.ArrayLike = a` does not cause any errors. What I believe is that the type-checker actually complains about the opposite: `ArrayLike` is not a `float` value. – cymin May 26 '22 at 17:10
  • Have a look at `clip` typing in [numpy source](https://github.com/numpy/numpy/blob/266aad7478bc7fbcc55eea7f942a0d373b838396/numpy/core/fromnumeric.pyi#L383=). It might help. Note that you cannot use `float` as `out`, but your code will try (and hit `TypeError`). Also I'd suggest adding defaults to overloads (with `...`) when they are compatible. In general annotating overload implementation is a good idea, just using unions of all possible parameter types. – STerliakov May 28 '22 at 15:47
  • Thanks, but in my actual code `array` is a property and the setter makes sure, that it actually is a `np.ndarray`. The problem still persists. Could you please elaborate on why you suggest adding the defaults if compatible? Is that not only an additional possibility for data getting out of sync (e.g. if I change a default in the implementation but forget to do so in the overload)? – cymin Jun 04 '22 at 16:46

0 Answers0