Repository: scipy/scipy
File: scipy/_lib/_array_api_override.py (Python)

Code before:
class _ArrayClsInfo(enum.Enum):
    skip = 0
    numpy = 1
    array_like = 2
    unknown = 3


@lru_cache(100)
def _validate_array_cls(cls: type) -> _ArrayClsInfo:
    if issubclass(cls, list | tuple):
        return _ArrayClsInfo.array_like

    # this comes from `_util._asarray_validated`
    if issubclass(cls, SparseABC):
        msg = ('Sparse arrays/matrices are not supported by this function. '
                'Perhaps one of the `scipy.sparse.linalg` functions '
                'would work instead.')
        raise ValueError(msg)

    if issubclass(cls, np.ma.MaskedArray):
        raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.")

    if issubclass(cls, np.matrix):
        raise TypeError("Inputs of type `numpy.matrix` are not supported.")

    if issubclass(cls, np.ndarray | np.generic):
        return _ArrayClsInfo.numpy

    # Note: this must happen after the test for np.generic, because
    # np.float64 and np.complex128 are subclasses of float and complex respectively.
    # This matches the behavior of array_api_compat.
    if issubclass(cls, int | float | complex | bool | type(None)):
        return _ArrayClsInfo.skip

    return _ArrayClsInfo.unknown


def array_namespace(*arrays: Array) -> ModuleType:
    """Get the array API compatible namespace for the arrays xs.

    Parameters
    ----------
    *arrays : sequence of array_like
        Arrays used to infer the common namespace.

    Returns
    -------
    namespace : module
        Common namespace.

    Notes

Review comment (performance):
I'm not sure if this is a meaningful amount of performance in the big picture, but

```
issubclass(cls, (x, y))
```

is slightly faster than

```
issubclass(cls, x | y)
```

Micro-benchmark:

```python
import scipy
import numpy as np

my_array = np.zeros((10, 10))
my_matrix = scipy.sparse.coo_matrix(my_array)

M_type = type(my_array)
%timeit issubclass(M_type, np.ndarray | np.generic)
# 417 ns ± 2.94 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit issubclass(M_type, (np.ndarray, np.generic))
# 224 ns ± 1.37 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

# Difference increases with more types checked
M_type = type(1234)
%timeit issubclass(M_type, int | float | complex | bool | type(None))
# 1.11 µs ± 4.59 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit issubclass(M_type, (int, float, complex, bool, type(None)))
# 299 ns ± 2.57 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
```

You should consider whether to use `issubclass(cls, (x, y))` here in the three places where you check whether a class is a subclass of a union of types. Alternatively, you could decide that the performance inside the cached function doesn't matter. I have no strong feelings one way or another.

Code after:
class _ArrayClsInfo(enum.Enum):
    skip = 0
    numpy = 1
    array_like = 2
    unknown = 3


@lru_cache(100)
def _validate_array_cls(cls: type) -> _ArrayClsInfo:
    if issubclass(cls, (list,  tuple)):
        return _ArrayClsInfo.array_like

    # this comes from `_util._asarray_validated`
    if issubclass(cls, SparseABC):
        msg = ('Sparse arrays/matrices are not supported by this function. '
                'Perhaps one of the `scipy.sparse.linalg` functions '
                'would work instead.')
        raise ValueError(msg)

    if issubclass(cls, np.ma.MaskedArray):
        raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.")

    if issubclass(cls, np.matrix):
        raise TypeError("Inputs of type `numpy.matrix` are not supported.")

    if issubclass(cls, (np.ndarray, np.generic)):
        return _ArrayClsInfo.numpy

    # Note: this must happen after the test for np.generic, because
    # np.float64 and np.complex128 are subclasses of float and complex respectively.
    # This matches the behavior of array_api_compat.
    if issubclass(cls, (int, float, complex, bool, type(None))):
        return _ArrayClsInfo.skip

    return _ArrayClsInfo.unknown


def array_namespace(*arrays: Array) -> ModuleType:
    """Get the array API compatible namespace for the arrays xs.

    Parameters
    ----------
    *arrays : sequence of array_like
        Arrays used to infer the common namespace.

    Returns
    -------
    namespace : module
        Common namespace.

    Notes