Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install "ruff<0.16"
pip install "ruff"
# Update output format to enable automatic inline annotations.
- name: Run Ruff
run: ruff check --output-format=github .
117 changes: 58 additions & 59 deletions array_api_strict/_array_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,8 @@ def _validate_index(
"zero-dimensional integer arrays and boolean arrays "
"are specified in the Array API."
)
if op == "setitem":
if isinstance(i, Array) and i.dtype in _integer_dtypes:
raise IndexError("Fancy indexing __setitem__ is not supported.")
if op == "setitem" and isinstance(i, Array) and i.dtype in _integer_dtypes:
raise IndexError("Fancy indexing __setitem__ is not supported.")

nonexpanding_key = []
single_axes = []
Expand Down Expand Up @@ -530,8 +529,8 @@ def __add__(self, other: Array | complex, /) -> Array:
other = self._check_allowed_dtypes(other, "numeric", "__add__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__add__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__add__(other._array)
return self.__class__._new(res, device=self.device)

def __and__(self, other: Array | int, /) -> Array:
Expand All @@ -542,8 +541,8 @@ def __and__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer or boolean", "__and__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__and__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__and__(other._array)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This pattern (repeated multiple times below) looks usunsual enough, thus I looked a bit more carefully.
The whole _normalize_two_args dance is needed to work around 0D numpy arrays lacking functionality: self._normalize_two_args(self, other) return a pair of -strict arrays, wher each element is either the original argument, or a new -strict array with its internal _array attribute promoted from 0D to 1D. Then, next line, {self, _self}._array.__op__(other._array) does the __op__ with numpy attrbutes, and the next line is return self.__class__._new(res, device=self.device), so we return a new -strict array object.

Either way, we do operations with numpy _array attributes and always return a new -strict array object---the actual operations are done with numpy _array attributes, and this is where it is decided if the actual operation is in-place or not.

Before this patch, the code did potentially mutate self, which was harmless because we always a new object constructed in return self.__class__._new.
This patch only makes the intent clearer by renaming the potentially mutated object.
Conclusion: it is a good change.

return self.__class__._new(res, device=self.device)

def __array_namespace__(self, /, *, api_version: str | None = None) -> ModuleType:
Expand Down Expand Up @@ -641,8 +640,8 @@ def __eq__(self, other: Array | complex, /) -> Array: # type: ignore[override]
other = self._check_allowed_dtypes(other, "all", "__eq__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__eq__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__eq__(other._array)
return self.__class__._new(res, device=self.device)

def __float__(self) -> float:
Expand All @@ -665,8 +664,8 @@ def __floordiv__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__floordiv__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__floordiv__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__floordiv__(other._array)
return self.__class__._new(res, device=self.device)

def __ge__(self, other: Array | float, /) -> Array:
Expand All @@ -677,8 +676,8 @@ def __ge__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__ge__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__ge__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__ge__(other._array)
return self.__class__._new(res, device=self.device)

def __getitem__(
Expand Down Expand Up @@ -729,8 +728,8 @@ def __gt__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__gt__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__gt__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__gt__(other._array)
return self.__class__._new(res, device=other.device)

def __int__(self) -> int:
Expand Down Expand Up @@ -784,8 +783,8 @@ def __le__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__le__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__le__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__le__(other._array)
return self.__class__._new(res, device=self.device)

def __lshift__(self, other: Array | int, /) -> Array:
Expand All @@ -796,8 +795,8 @@ def __lshift__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer", "__lshift__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__lshift__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__lshift__(other._array)
return self.__class__._new(res, device=self.device)

def __lt__(self, other: Array | float, /) -> Array:
Expand All @@ -808,8 +807,8 @@ def __lt__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__lt__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__lt__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__lt__(other._array)
return self.__class__._new(res, device=self.device)

def __matmul__(self, other: Array, /) -> Array:
Expand All @@ -833,8 +832,8 @@ def __mod__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__mod__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__mod__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__mod__(other._array)
return self.__class__._new(res, device=self.device)

def __mul__(self, other: Array | complex, /) -> Array:
Expand All @@ -845,8 +844,8 @@ def __mul__(self, other: Array | complex, /) -> Array:
other = self._check_allowed_dtypes(other, "numeric", "__mul__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__mul__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__mul__(other._array)
return self.__class__._new(res, device=self.device)

def __ne__(self, other: Array | complex, /) -> Array: # type: ignore[override]
Expand All @@ -857,8 +856,8 @@ def __ne__(self, other: Array | complex, /) -> Array: # type: ignore[override]
other = self._check_allowed_dtypes(other, "all", "__ne__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__ne__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__ne__(other._array)
return self.__class__._new(res, device=self.device)

def __neg__(self) -> Array:
Expand All @@ -878,8 +877,8 @@ def __or__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer or boolean", "__or__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__or__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__or__(other._array)
return self.__class__._new(res, device=self.device)

def __pos__(self) -> Array:
Expand Down Expand Up @@ -913,8 +912,8 @@ def __rshift__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer", "__rshift__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__rshift__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rshift__(other._array)
return self.__class__._new(res, device=self.device)

def __setitem__(
Expand Down Expand Up @@ -962,8 +961,8 @@ def __sub__(self, other: Array | complex, /) -> Array:
other = self._check_allowed_dtypes(other, "numeric", "__sub__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__sub__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__sub__(other._array)
return self.__class__._new(res, device=self.device)

# PEP 484 requires int to be a subtype of float, but __truediv__ should
Expand All @@ -976,8 +975,8 @@ def __truediv__(self, other: Array | complex, /) -> Array:
other = self._check_allowed_dtypes(other, "floating-point", "__truediv__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__truediv__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__truediv__(other._array)
return self.__class__._new(res, device=self.device)

def __xor__(self, other: Array | int, /) -> Array:
Expand All @@ -988,8 +987,8 @@ def __xor__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer or boolean", "__xor__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__xor__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__xor__(other._array)
return self.__class__._new(res, device=self.device)

def __iadd__(self, other: Array | complex, /) -> Self:
Expand All @@ -1011,8 +1010,8 @@ def __radd__(self, other: Array | complex, /) -> Array:
other = self._check_allowed_dtypes(other, "numeric", "__radd__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__radd__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__radd__(other._array)
return self.__class__._new(res, device=self.device)

def __iand__(self, other: Array | int, /) -> Self:
Expand All @@ -1034,8 +1033,8 @@ def __rand__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer or boolean", "__rand__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__rand__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rand__(other._array)
return self.__class__._new(res, device=self.device)

def __ifloordiv__(self, other: Array | float, /) -> Self:
Expand All @@ -1057,8 +1056,8 @@ def __rfloordiv__(self, other: Array | float, /) -> Array:
other = self._check_allowed_dtypes(other, "real numeric", "__rfloordiv__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__rfloordiv__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rfloordiv__(other._array)
return self.__class__._new(res, device=self.device)

def __ilshift__(self, other: Array | int, /) -> Self:
Expand All @@ -1080,8 +1079,8 @@ def __rlshift__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer", "__rlshift__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__rlshift__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rlshift__(other._array)
return self.__class__._new(res, device=self.device)

def __imatmul__(self, other: Array, /) -> Self:
Expand Down Expand Up @@ -1129,8 +1128,8 @@ def __rmod__(self, other: Array | float, /) -> Array:
if other is NotImplemented:
return other
self._check_type_device(other)
self, other = self._normalize_two_args(self, other)
res = self._array.__rmod__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rmod__(other._array)
return self.__class__._new(res, device=self.device)

def __imul__(self, other: Array | complex, /) -> Self:
Expand All @@ -1152,8 +1151,8 @@ def __rmul__(self, other: Array | complex, /) -> Array:
if other is NotImplemented:
return other
self._check_type_device(other)
self, other = self._normalize_two_args(self, other)
res = self._array.__rmul__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rmul__(other._array)
return self.__class__._new(res, device=self.device)

def __ior__(self, other: Array | int, /) -> Self:
Expand All @@ -1175,8 +1174,8 @@ def __ror__(self, other: Array | int, /) -> Array:
other = self._check_allowed_dtypes(other, "integer or boolean", "__ror__")
if other is NotImplemented:
return other
self, other = self._normalize_two_args(self, other)
res = self._array.__ror__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__ror__(other._array)
return self.__class__._new(res, device=self.device)

def __ipow__(self, other: Array | complex, /) -> Self:
Expand Down Expand Up @@ -1223,8 +1222,8 @@ def __rrshift__(self, other: Array | int, /) -> Array:
if other is NotImplemented:
return other
self._check_type_device(other)
self, other = self._normalize_two_args(self, other)
res = self._array.__rrshift__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rrshift__(other._array)
return self.__class__._new(res, device=self.device)

def __isub__(self, other: Array | complex, /) -> Self:
Expand All @@ -1246,8 +1245,8 @@ def __rsub__(self, other: Array | complex, /) -> Array:
if other is NotImplemented:
return other
self._check_type_device(other)
self, other = self._normalize_two_args(self, other)
res = self._array.__rsub__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rsub__(other._array)
return self.__class__._new(res, device=self.device)

def __itruediv__(self, other: Array | complex, /) -> Self:
Expand All @@ -1269,8 +1268,8 @@ def __rtruediv__(self, other: Array | complex, /) -> Array:
if other is NotImplemented:
return other
self._check_type_device(other)
self, other = self._normalize_two_args(self, other)
res = self._array.__rtruediv__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rtruediv__(other._array)
return self.__class__._new(res, device=self.device)

def __ixor__(self, other: Array | int, /) -> Self:
Expand All @@ -1292,8 +1291,8 @@ def __rxor__(self, other: Array | int, /) -> Array:
if other is NotImplemented:
return other
self._check_type_device(other)
self, other = self._normalize_two_args(self, other)
res = self._array.__rxor__(other._array)
_self, other = self._normalize_two_args(self, other)
res = _self._array.__rxor__(other._array)
return self.__class__._new(res, device=self.device)

def to_device(self, device: Device, /, stream: None = None) -> Array:
Expand Down
2 changes: 1 addition & 1 deletion array_api_strict/_manipulation_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def roll(
See its docstring for more information.
"""
if not isinstance(shift, int | tuple):
raise ValueError(
raise TypeError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Strictly speaking, this is a backwards incompatible change.
That said, TypeError makes more sense, and spot-checking several "real" frameworks shows that a TypeError is more common (exceptions: np.roll(np.eye(3), "oops") raises a ValueError; jnp.roll(jnp.eye(3), None) raise a ValueError too --- these are edge cases though).

The spec does not mandate a specific exception class, thus we can accept the backwards compat break and make change it to a more appropriate exception type, as done here.

f"`shift` can only be an int or a tuple, got {type(shift)=} instead."
)
return Array._new(np.roll(x._array, shift, axis=axis), device=x.device)
Expand Down
Loading
Loading