Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAINT: finfo() / iinfo() input/output review #143

Merged
merged 1 commit into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions array_api_strict/_data_type_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def can_cast(from_: DType | Array, to: DType, /) -> bool:

# These are internal objects for the return types of finfo and iinfo, since
# the NumPy versions contain extra data that isn't part of the spec.
@dataclass
# There should be no expectation for them to be comparable, hashable, or writeable.

@dataclass(frozen=True, eq=False)
class finfo_object:
bits: int
# Note: The types of the float data here are float, whereas in NumPy they
Expand All @@ -111,22 +113,32 @@ class finfo_object:
smallest_normal: float
dtype: DType

__hash__ = NotImplemented


@dataclass
@dataclass(frozen=True, eq=False)
class iinfo_object:
bits: int
max: int
min: int
dtype: DType

__hash__ = NotImplemented


def finfo(type: DType | Array, /) -> finfo_object:
"""
Array API compatible wrapper for :py:func:`np.finfo <numpy.finfo>`.

See its docstring for more information.
"""
np_type = type._array if isinstance(type, Array) else type._np_dtype
if isinstance(type, Array):
np_type = type._dtype._np_dtype
Comment on lines +135 to +136
Copy link
Contributor Author

@crusaderky crusaderky Apr 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be tested in array-api-tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elif isinstance(type, DType):
np_type = type._np_dtype
else:
raise TypeError(f"'type' must be a dtype or array, not {type!r}")

fi = np.finfo(np_type)
# Note: The types of the float data here are float, whereas in NumPy they
# are scalars of the corresponding float dtype.
Expand All @@ -146,7 +158,13 @@ def iinfo(type: DType | Array, /) -> iinfo_object:

See its docstring for more information.
"""
np_type = type._array if isinstance(type, Array) else type._np_dtype
if isinstance(type, Array):
np_type = type._dtype._np_dtype
elif isinstance(type, DType):
np_type = type._np_dtype
else:
raise TypeError(f"'type' must be a dtype or array, not {type!r}")

ii = np.iinfo(np_type)
return iinfo_object(ii.bits, ii.max, ii.min, DType(ii.dtype))

Expand Down
2 changes: 1 addition & 1 deletion array_api_strict/_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __eq__(self, other: object) -> builtins.bool:
stacklevel=2,
)
if not isinstance(other, DType):
return NotImplemented
return False
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not want Python to try other.__eq__(self)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to run scipy tests against this branch. I doubt any of the behaviors prohibited here is relied on, but we've been surprised more than once, so...

return self._np_dtype == other._np_dtype

def __hash__(self) -> int:
Expand Down
46 changes: 40 additions & 6 deletions array_api_strict/tests/test_data_type_functions.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import warnings

import numpy as np
import pytest

from numpy.testing import assert_raises
import numpy as np

from .._creation_functions import asarray
from .._data_type_functions import astype, can_cast, isdtype, result_type
from .._dtypes import (
bool, int8, int16, uint8, float64, int64
)
from .._data_type_functions import astype, can_cast, finfo, iinfo, isdtype, result_type
from .._dtypes import bool, float64, int8, int16, int64, uint8
from .._flags import set_array_api_strict_flags


Expand Down Expand Up @@ -88,3 +85,40 @@ def test_result_type_py_scalars(api_version):

with pytest.raises(TypeError):
result_type(int64, True)


def test_finfo_iinfo_dtypelike():
"""np.finfo() and np.iinfo() accept any DTypeLike.
Array API only accepts Array | DType.
"""
match = "must be a dtype or array"
with pytest.raises(TypeError, match=match):
finfo("float64")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got to admit I do miss the ability to use dtype=float. Nothing to do about it though.

with pytest.raises(TypeError, match=match):
finfo(float)
with pytest.raises(TypeError, match=match):
iinfo("int8")
with pytest.raises(TypeError, match=match):
iinfo(int)


def test_finfo_iinfo_wrap_output():
"""Test that the finfo(...).dtype and iinfo(...).dtype
are array-api-strict.DType objects; not numpy.dtype.
"""
# Note: array_api_strict.DType objects are not singletons
assert finfo(float64).dtype == float64
assert iinfo(int8).dtype == int8
Comment on lines +105 to +111
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also ought to be in array-api-tests, TBH



@pytest.mark.parametrize("func,arg", [(finfo, float64), (iinfo, int8)])
def test_finfo_iinfo_output_assumptions(func, arg):
"""There should be no expectation for the output of finfo()/iinfo()
to be comparable, hashable, or writeable.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...or serializable, or have a __dict__, or being targetable by weakref, and probably a few more.
These I think are the important ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't bother testing against them behaving like namedtuples because numpy ones aren't anyway

"""
obj = func(arg)
assert obj != func(arg) # Defaut behaviour for custom classes
with pytest.raises(TypeError):
hash(obj)
with pytest.raises(Exception, match="cannot assign"):
obj.min = 0
Loading