Skip to content

ENH: Make numpy scalars subtypes of their builtin counterpart during type checking #17105

@BvB93

Description

@BvB93

This issue proposes making np.generics subclasses of their respective builtin counterpart during the process type checking.

Numpy has a number of np.generic subclasses which is serve as counterpart to certain builtin objects (e.g. builtins.int & np.int64) and whenever a builtin scalar is expected it can generally be substituted for its numpy-based counterpart.
One of the situations where things become more difficult is static typing, as this requires explicit specifying both classes as valid types, lest a passed object might be rejected static type checkers such as mypy:

In [1]: import numpy as np
   ...: from typing import Union

In [2]: def func(a: Union[int, np.int64]) -> None:  # This can quickly get rather ugly
   ...:     pass
   ...:

#17096 was created in order to make this process a bit smoother; so that the same Unions don't have to be redefined every single time. However, a simpler solution (which I'm proposing here instead) would be to make certain np.generic subclasses a subclass of their respective builtin counterpart for the purpose of type checking (this will not affect the classes' runtime behavior).
Note that mypy already does something similar for the buitin numerical types, .e.g. it considers int a subclass of floatandfloata subclass ofcomplex` (even though this is, strictly speaking, incorrect).

This approach has the following advantages:

  1. It improves general readability: Union[cls_builtin, cls_np] simply becomes cls_builtin.
  2. It takes advantage of the fact that, generally speaking, builtin scalars can be substituted for their np.generic-based counterpart without issue.
  3. It will substantially increase the compatibility of numpy scalars with functions as typed by third-parties. To illustrate this point the example below provides a very reasonable annotated function; yet (in its current state) the likes of mypy will raise an error here.
In [1]: import numpy as np

In [2]: def func(a: int) -> int:
   ...:     return a * 2
   ...:

In [3]: func(np.int64(1))  # mypy(error): Argument 1 to "func" has incompatible type "int64"; expected "int"
Out[3]: 3

Example Implementation

class complexfloating(np.inexact, complex): ...
    ...

class str_(np.character, str):
    ...

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions