ndlike 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ndlike/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Tools for defining custom array types across NumPy, PyTorch, and JAX."""
@@ -0,0 +1,208 @@
1
+ """An extensible switch dispatch mechanism.
2
+
3
+ Akin to the builtin functools.singledispatch mechanism,
4
+ but with an equality-based dispatcher.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Iterator, MutableMapping
10
+ from functools import update_wrapper
11
+ from typing import TYPE_CHECKING, Concatenate, Protocol, overload, override
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Callable, Iterable
15
+ from types import NotImplementedType
16
+
17
+
18
+ class _RegistrationSink[K, V](Protocol):
19
+ """The contravariant write interface used to constrain registered values."""
20
+
21
+ def _register_value(self, key: K, value: V) -> None: ...
22
+
23
+
24
+ class _Registration[V]:
25
+ """Store a value while preserving its exact type and identity."""
26
+
27
+ def __init__(self, store: Callable[[V], None]) -> None:
28
+ self._store = store
29
+
30
+ # A bound such as ``W: V`` is not legal: TypeVar bounds cannot be generic.
31
+ # Contravariant storage constrains W to accepted values without widening it
32
+ # to V, preserving generic classes, overloads, and callable-object attributes.
33
+ def __call__[W](self: _Registration[W], value: W) -> W:
34
+ self._store(value)
35
+ return value
36
+
37
+
38
+ class _FunctionRegistration[F]:
39
+ """Identity-preserving registration with the function decorator's ``f`` keyword."""
40
+
41
+ def __init__(self, store: Callable[[F], None]) -> None:
42
+ self._store = store
43
+
44
+ def __call__[W](self: _FunctionRegistration[W], f: W) -> W:
45
+ self._store(f)
46
+ return f
47
+
48
+
49
+ class switchdispatch[T, **In, Out]: # noqa: N801
50
+ """A switch dispatch decorator.
51
+
52
+ Similar to functools.singledispatch, but dispatches based on equality
53
+ rather than type.
54
+
55
+ Example:
56
+ >>> @switchdispatch
57
+ >>> def func(x):
58
+ >>> return "default"
59
+ >>>
60
+ >>> @func.register(1)
61
+ >>> def _(x):
62
+ >>> return "one"
63
+ >>>
64
+ >>> @func.register(2)
65
+ >>> def _(x):
66
+ >>> return "two"
67
+ >>>
68
+ >>> print(func(1)) # Output: "one"
69
+ >>> print(func(2)) # Output: "two"
70
+ >>> print(func(3)) # Output: "default"
71
+ """
72
+
73
+ def __init__(self, func: Callable[Concatenate[T, In], Out]) -> None:
74
+ """Initialize the switchdispatch with the default function."""
75
+ self._func = func
76
+ self._registry: dict[object, Callable[Concatenate[T, In], Out]] = {}
77
+ update_wrapper(self, func, updated=())
78
+
79
+ @overload
80
+ def register(self, key: T) -> _FunctionRegistration[Callable[Concatenate[T, In], Out]]: ...
81
+
82
+ @overload
83
+ def register[F](self: _RegistrationSink[T, F], key: T, f: F) -> F: ...
84
+
85
+ def register(
86
+ self,
87
+ key: object,
88
+ f: Callable[Concatenate[T, In], Out] | None = None,
89
+ ) -> Callable[Concatenate[T, In], Out] | _FunctionRegistration[Callable[Concatenate[T, In], Out]]:
90
+ """Register a function for the given key, returning it unchanged."""
91
+ if f is None:
92
+ return self.multi_register([key])
93
+ return self.multi_register([key], f)
94
+
95
+ @overload
96
+ def multi_register(self, keys: Iterable[object]) -> _FunctionRegistration[Callable[Concatenate[T, In], Out]]: ...
97
+
98
+ @overload
99
+ def multi_register[F](self: _RegistrationSink[object, F], keys: Iterable[object], f: F) -> F: ...
100
+
101
+ def multi_register(
102
+ self,
103
+ keys: Iterable[object],
104
+ f: Callable[Concatenate[T, In], Out] | None = None,
105
+ ) -> Callable[Concatenate[T, In], Out] | _FunctionRegistration[Callable[Concatenate[T, In], Out]]:
106
+ """Register a function for the given keys, returning it unchanged."""
107
+ if f is not None:
108
+ for key in keys:
109
+ self._register_value(key, f)
110
+ return f
111
+
112
+ def store(f: Callable[Concatenate[T, In], Out]) -> None:
113
+ for key in keys:
114
+ self._register_value(key, f)
115
+
116
+ return _FunctionRegistration(store)
117
+
118
+ def _register_value(self, key: object, value: Callable[Concatenate[T, In], Out]) -> None:
119
+ self._registry[key] = value
120
+
121
+ def __call__(self, arg: T, *args: In.args, **kwargs: In.kwargs) -> Out:
122
+ """Call the appropriate function based on the argument."""
123
+ func_impl = self._registry.get(arg, self._func)
124
+ return func_impl(arg, *args, **kwargs)
125
+
126
+
127
+ class switch[K, V](MutableMapping[K, V]): # noqa: N801
128
+ """A simple switch-case mechanism based on a dictionary."""
129
+
130
+ _registry: dict[K, V]
131
+
132
+ def __init__(
133
+ self,
134
+ cases: dict[K, V] | None = None,
135
+ ) -> None:
136
+ """Initialize the switch with the given cases and default value."""
137
+ self._registry = cases or {}
138
+
139
+ @overload
140
+ def register(self, key: K) -> _Registration[V]: ...
141
+
142
+ @overload
143
+ def register[W](self: _RegistrationSink[K, W], key: K, value: W) -> W: ...
144
+
145
+ def register(
146
+ self,
147
+ key: K,
148
+ value: V | NotImplementedType = NotImplemented,
149
+ ) -> _Registration[V] | V:
150
+ """Register a value for the given key, returning it unchanged."""
151
+ if value is NotImplemented:
152
+ return self.multi_register([key])
153
+ return self.multi_register([key], value)
154
+
155
+ @overload
156
+ def multi_register(self, keys: Iterable[K]) -> _Registration[V]: ...
157
+
158
+ @overload
159
+ def multi_register[W](self: _RegistrationSink[K, W], keys: Iterable[K], value: W) -> W: ...
160
+
161
+ def multi_register(
162
+ self,
163
+ keys: Iterable[K],
164
+ value: V | NotImplementedType = NotImplemented,
165
+ ) -> _Registration[V] | V:
166
+ """Register a value for the given keys, returning it unchanged."""
167
+ if value is not NotImplemented:
168
+ for key in keys:
169
+ self._register_value(key, value)
170
+ return value
171
+
172
+ def store(value: V) -> None:
173
+ for key in keys:
174
+ self._register_value(key, value)
175
+
176
+ return _Registration(store)
177
+
178
+ def _register_value(self, key: K, value: V) -> None:
179
+ self._registry[key] = value
180
+
181
+ @override
182
+ def __getitem__(self, key: K) -> V:
183
+ """Return the value corresponding to the given key."""
184
+ return self._registry[key]
185
+
186
+ def __call__(self, key: K) -> V:
187
+ """Return the value corresponding to the given key."""
188
+ return self._registry[key]
189
+
190
+ @override
191
+ def __setitem__(self, key: K, value: V) -> None:
192
+ """Set the value for the given key."""
193
+ self._registry[key] = value
194
+
195
+ @override
196
+ def __delitem__(self, key: K) -> None:
197
+ """Delete the value for the given key."""
198
+ del self._registry[key]
199
+
200
+ @override
201
+ def __iter__(self) -> Iterator[K]:
202
+ """Return an iterator over the keys in the switch cases."""
203
+ return iter(self._registry)
204
+
205
+ @override
206
+ def __len__(self) -> int:
207
+ """Return the number of cases in the switch."""
208
+ return len(self._registry)
ndlike/array_like.py ADDED
@@ -0,0 +1,471 @@
1
+ """Protocol for ndarray-like objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Callable, Iterator, Sequence
7
+ from types import EllipsisType, ModuleType
8
+ from typing import TYPE_CHECKING, Any, Literal, Protocol, Self, SupportsIndex, override, runtime_checkable
9
+
10
+ from flextype import flexdispatch
11
+ import numpy as np
12
+
13
+ if TYPE_CHECKING:
14
+ from numpy.typing import ArrayLike as NpArrayLike, DTypeLike, NDArray
15
+
16
+
17
+ @runtime_checkable
18
+ class _SupportsArray[T: np.dtype](Protocol):
19
+ def __array__(self) -> np.ndarray[Any, T]: ...
20
+
21
+
22
+ type _NestedSequence[T] = T | Sequence[_NestedSequence[T]]
23
+
24
+ type _DualArrayLike[DT: np.dtype, T] = _NestedSequence[_SupportsArray[DT]] | _NestedSequence[T]
25
+ type _ArrayLikeInt_co = _DualArrayLike[np.dtype[np.bool | np.integer], int]
26
+ type ToIndex = SupportsIndex | slice | EllipsisType | _ArrayLikeInt_co | None
27
+ type ToIndices = ToIndex | tuple[ToIndex, ...]
28
+ type Order = Literal["C", "F", "A", "K"]
29
+ type ArrayAPIVersion = Literal["2022.12", "2023.12", "2024.12", "2025.12"]
30
+
31
+ _GetFlag = Literal[
32
+ "C",
33
+ "CONTIGUOUS",
34
+ "C_CONTIGUOUS",
35
+ "F",
36
+ "FORTRAN",
37
+ "F_CONTIGUOUS",
38
+ "W",
39
+ "WRITEABLE",
40
+ "B",
41
+ "BEHAVED",
42
+ "O",
43
+ "OWNDATA",
44
+ "A",
45
+ "ALIGNED",
46
+ "X",
47
+ "WRITEBACKIFCOPY",
48
+ "CA",
49
+ "CARRAY",
50
+ "FA",
51
+ "FARRAY",
52
+ "FNC",
53
+ "FORC",
54
+ ]
55
+
56
+ _SetFlag = Literal[
57
+ "A",
58
+ "ALIGNED",
59
+ "W",
60
+ "WRITEABLE",
61
+ "X",
62
+ "WRITEBACKIFCOPY",
63
+ ]
64
+
65
+
66
+ class ArrayFlagsLike(Protocol):
67
+ """Protocol for array flags."""
68
+
69
+ aligned: bool
70
+ writeable: bool
71
+ writebackifcopy: bool
72
+
73
+ @property
74
+ def behaved(self) -> bool:
75
+ """True if the array is well-behaved, i.e. it is aligned and writeable."""
76
+
77
+ @property
78
+ def c_contiguous(self) -> bool:
79
+ """True if the array is C-contiguous."""
80
+
81
+ @property
82
+ def carray(self) -> bool:
83
+ """True if the array is C-contiguous."""
84
+
85
+ @property
86
+ def contiguous(self) -> bool:
87
+ """True if the array is contiguous."""
88
+
89
+ @property
90
+ def f_contiguous(self) -> bool:
91
+ """True if the array is Fortran-contiguous."""
92
+
93
+ @property
94
+ def farray(self) -> bool:
95
+ """True if the array is Fortran-contiguous."""
96
+
97
+ @property
98
+ def fnc(self) -> bool:
99
+ """True if the array is Fortran-contiguous but not C-contiguous."""
100
+
101
+ @property
102
+ def forc(self) -> bool:
103
+ """True if the array is C-contiguous or Fortran-contiguous."""
104
+
105
+ @property
106
+ def fortran(self) -> bool:
107
+ """True if the array is Fortran-contiguous."""
108
+
109
+ @property
110
+ def owndata(self) -> bool:
111
+ """True if the array owns its own data."""
112
+
113
+ def __getitem__(self, key: _GetFlag, /) -> bool:
114
+ """Get the value of a flag."""
115
+
116
+ def __setitem__(self, key: _SetFlag, value: bool, /) -> None:
117
+ """Set the value of a flag."""
118
+
119
+
120
+ @runtime_checkable
121
+ class ArrayLikeWithoutSize[DT](Protocol):
122
+ """Core array protocol shared by NumPy, JAX, and PyTorch.
123
+
124
+ This protocol omits ``size`` because PyTorch exposes it as a method rather than
125
+ the integer element-count property used by NumPy and JAX.
126
+
127
+ Unlike `numpy.typing.ArrayLike`, this protocol does not only check
128
+ whether an object can be converted to a numpy array, but rather
129
+ whether it supports core indexing and shape/metadata operations similar to a numpy array.
130
+ This protocol is based in the Python array API standard: https://data-apis.org/array-api
131
+ Unlike the official standard, this protocol does not require support for all numerical operations.
132
+ """
133
+
134
+ def __array__(self, dtype: DTypeLike | None = None, /, *, copy: bool | None = None) -> np.ndarray:
135
+ """Convert to a numpy array."""
136
+
137
+ def __getitem__(self, index: ToIndices, /) -> Any: # noqa: ANN401
138
+ """Return values selected by index."""
139
+
140
+ def __setitem__(self, index: ToIndices, value: NpArrayLike, /) -> None:
141
+ """Set values selected by index."""
142
+
143
+ def __array_namespace__(self, /, *, api_version: ArrayAPIVersion | None = None) -> ModuleType:
144
+ """Return the namespace of the array, e.g. 'numpy' or 'torch'."""
145
+
146
+ def to_device(self, device: Literal["cpu"], /, *, stream: int | Any | None = None) -> Self: # noqa: ANN401
147
+ """Move the array to a device."""
148
+
149
+ @property
150
+ def ndim(self) -> int:
151
+ """Number of dimensions."""
152
+
153
+ @property
154
+ def shape(self) -> tuple[int, ...]:
155
+ """Shape of the array."""
156
+
157
+ @property
158
+ def dtype(self) -> Any: # noqa: ANN401
159
+ """Data type of the array."""
160
+
161
+ @property
162
+ def device(self) -> Any: # noqa: ANN401
163
+ """Device of the array."""
164
+
165
+ @property
166
+ def T(self) -> Any: # noqa: ANN401, N802
167
+ """Transposed array."""
168
+
169
+ @property
170
+ def mT(self) -> Any: # noqa: ANN401, N802
171
+ """Matrix transposed array."""
172
+
173
+ def __len__(self) -> int:
174
+ """Length along the first axis."""
175
+
176
+ def __iter__(self) -> Iterator[Any]:
177
+ """Iterator over the first axis."""
178
+
179
+
180
+ @runtime_checkable
181
+ class ArrayLike[DT](ArrayLikeWithoutSize[DT], Protocol):
182
+ """Core array protocol extended with an integer element-count property.
183
+
184
+ Unlike `numpy.typing.ArrayLike`, this protocol does not only check
185
+ whether an object can be converted to a numpy array, but rather
186
+ whether it supports core indexing and shape/metadata operations similar to a numpy array.
187
+ This protocol is based in the Python array API standard: https://data-apis.org/array-api
188
+ Unlike the official standard, this protocol does not require support for all numerical operations.
189
+ """
190
+
191
+ @property
192
+ def size(self) -> int:
193
+ """Number of elements in the array."""
194
+
195
+
196
+ @runtime_checkable
197
+ class NumpyLikeConvertible[DT](ArrayLike[DT], Protocol):
198
+ """Protocol for array-like objects that can be converted to numpy arrays."""
199
+
200
+ def __array_like__(self, dtype: DTypeLike | None = None, /, *, copy: bool | None = None) -> NumpyLike[Any]:
201
+ """Convert to a NumpyLike."""
202
+
203
+
204
+ @runtime_checkable
205
+ class NumpyLike[DT](ArrayLike[DT], Protocol):
206
+ """Protocol for array-like objects that implement NumPy-specific APIs."""
207
+
208
+ @property
209
+ def dtype(self) -> np.dtype:
210
+ """Data type of the array."""
211
+
212
+ def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any) -> Any: # noqa: ANN401
213
+ """Handle numpy ufuncs."""
214
+
215
+ def __array_function__(
216
+ self,
217
+ func: Callable,
218
+ types: tuple[type[Any], ...],
219
+ args: tuple[Any, ...],
220
+ kwargs: dict[str, Any],
221
+ ) -> Any: # noqa: ANN401
222
+ """Handle numpy array functions."""
223
+
224
+ @property
225
+ def mT(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
226
+ """Matrix-transposed view."""
227
+
228
+ @property
229
+ def T(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
230
+ """Transposed view."""
231
+
232
+ def transpose(self, *axes: int | None) -> NumpyLike[Any] | np.ndarray:
233
+ """Return transposed array."""
234
+
235
+ def copy(self, order: Order = "C") -> NumpyLike[Any] | np.ndarray:
236
+ """Return array copy."""
237
+
238
+ def astype(
239
+ self,
240
+ dtype: DTypeLike,
241
+ order: Order = "K",
242
+ casting: Literal["no", "equiv", "safe", "same_kind", "unsafe"] = "unsafe",
243
+ subok: bool = True,
244
+ copy: bool = True,
245
+ ) -> NumpyLike[Any] | np.ndarray:
246
+ """Return cast array."""
247
+
248
+ @property
249
+ def flags(self) -> ArrayFlagsLike:
250
+ """The flags of the array."""
251
+
252
+
253
+ class NumpyLikeImplementation[DT: NumpyLike | np.ndarray](ArrayLike[DT], np.lib.mixins.NDArrayOperatorsMixin, ABC):
254
+ """ABC implementation for array-like objects that behave like NumPy arrays.
255
+
256
+ Representation-specific conversion and metadata are explicitly abstract rather
257
+ than inherited as protocol placeholders. Unsupported indexing, iteration and
258
+ scalar conversions retain their concrete ``NotImplementedError`` fallbacks.
259
+ """
260
+
261
+ @abstractmethod
262
+ def __array__(self, dtype: DTypeLike | None = None, /, *, copy: bool | None = None) -> np.ndarray:
263
+ """Convert to a NumPy array."""
264
+
265
+ @abstractmethod
266
+ def __array_namespace__(self, /, *, api_version: ArrayAPIVersion | None = None) -> ModuleType:
267
+ """Return the array namespace."""
268
+
269
+ @property
270
+ @abstractmethod
271
+ def device(self) -> Any: # noqa: ANN401
272
+ """Device of the array."""
273
+
274
+ @property
275
+ @abstractmethod
276
+ def ndim(self) -> int:
277
+ """Number of dimensions."""
278
+
279
+ @property
280
+ @abstractmethod
281
+ def shape(self) -> tuple[int, ...]:
282
+ """Shape of the array."""
283
+
284
+ @property
285
+ @abstractmethod
286
+ def size(self) -> int:
287
+ """Number of elements in the array."""
288
+
289
+ def __getitem__(self: Self, index: ToIndices, /) -> NumpyLikeImplementation[DT] | DT:
290
+ """Return a new array containing the indexed values."""
291
+ msg = f"{type(self).__name__} does not support indexing."
292
+ raise NotImplementedError(msg)
293
+
294
+ def __setitem__(self, index: ToIndices, value: object, /) -> None:
295
+ """Set the values at the given indices to the given value."""
296
+ msg = f"{type(self).__name__} does not support item assignment."
297
+ raise NotImplementedError(msg)
298
+
299
+ @override
300
+ @property
301
+ @abstractmethod
302
+ def dtype(self) -> np.dtype:
303
+ """Data type of the array."""
304
+
305
+ @abstractmethod
306
+ def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any) -> Any: # noqa: ANN401
307
+ """Handle numpy ufuncs.
308
+
309
+ Args:
310
+ ufunc: The ufunc to apply.
311
+ method: The method of the ufunc.
312
+ inputs: The input arrays.
313
+ kwargs: Additional keyword arguments.
314
+
315
+ Returns:
316
+ The result of applying the ufunc.
317
+ """
318
+
319
+ @abstractmethod
320
+ def __array_function__(
321
+ self,
322
+ func: Callable,
323
+ types: tuple[type[Any], ...],
324
+ args: tuple[Any, ...],
325
+ kwargs: dict[str, Any],
326
+ ) -> Any: # noqa: ANN401
327
+ """Handle numpy array functions.
328
+
329
+ Args:
330
+ func: The numpy function to apply.
331
+ types: The types of the input arguments.
332
+ args: The input arguments.
333
+ kwargs: Additional keyword arguments.
334
+
335
+ Returns:
336
+ The result of applying the numpy function.
337
+ """
338
+
339
+ @property
340
+ def mT(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
341
+ """The transposed version of the underlying array."""
342
+ return np.matrix_transpose(self)
343
+
344
+ @property
345
+ def T(self) -> NumpyLike[Any] | np.ndarray: # noqa: N802
346
+ """The transposed version of the underlying array."""
347
+ return np.transpose(self)
348
+
349
+ def transpose(self, *axes: int | None) -> NumpyLike[Any] | np.ndarray:
350
+ """Return a transposed version of the NumpySample.
351
+
352
+ This method implicitly also provides full axis tracking support for
353
+ - `np.moveaxis`
354
+ - `np.rollaxis`
355
+ Those functions call out to `transpose` methods for custom array types.
356
+
357
+ Args:
358
+ axes: The axes to transpose.
359
+
360
+ Returns:
361
+ A transposed version of the NumpySample.
362
+ """
363
+ if len(axes) == 0:
364
+ return np.transpose(self)
365
+ if len(axes) == 1 and not isinstance(axes[0], int):
366
+ return np.transpose(self, axes[0])
367
+ return np.transpose(self, axes) # ty:ignore[no-matching-overload]
368
+
369
+ def copy(self, order: Order = "C") -> NumpyLike[Any] | np.ndarray:
370
+ """Return a copy of the array."""
371
+ return np.copy(self, order=order, subok=True) # ty:ignore[no-matching-overload]
372
+
373
+ def astype(
374
+ self,
375
+ dtype: DTypeLike,
376
+ order: Order = "K",
377
+ casting: Literal["no", "equiv", "safe", "same_kind", "unsafe"] = "unsafe",
378
+ subok: bool = True,
379
+ copy: bool = True,
380
+ ) -> NumpyLike[Any] | np.ndarray:
381
+ """Copy of the array, cast to a specified type."""
382
+ if order != "K" or casting != "unsafe" or not subok:
383
+ msg = "Non-default ndarray.astype options require a representation-specific implementation."
384
+ raise NotImplementedError(msg)
385
+ return np.astype(self, dtype, copy=copy) # ty: ignore[no-matching-overload]
386
+
387
+ def __index__(self) -> int:
388
+ """Converts 0d integer array to a Python integer."""
389
+ msg = f"{type(self).__name__} does not support conversion to index."
390
+ raise NotImplementedError(msg)
391
+
392
+ def __int__(self) -> int:
393
+ """Converts 0d integer array to a Python integer."""
394
+ msg = f"{type(self).__name__} does not support conversion to int."
395
+ raise NotImplementedError(msg)
396
+
397
+ def __bool__(self) -> bool:
398
+ """Converts 0d boolean array to a Python boolean."""
399
+ msg = f"{type(self).__name__} does not support conversion to bool."
400
+ raise NotImplementedError(msg)
401
+
402
+ def __float__(self) -> float:
403
+ """Converts 0d float array to a Python float."""
404
+ msg = f"{type(self).__name__} does not support conversion to float."
405
+ raise NotImplementedError(msg)
406
+
407
+ def __complex__(self) -> complex:
408
+ """Converts 0d complex array to a Python complex."""
409
+ msg = f"{type(self).__name__} does not support conversion to complex."
410
+ raise NotImplementedError(msg)
411
+
412
+ def __len__(self) -> int:
413
+ """Returns the length of the array along the first dimension.
414
+
415
+ This is not mandated by the array API standard, but numpy, torch and jax all support it.
416
+ """
417
+ msg = f"{type(self).__name__} does not support len()."
418
+ raise NotImplementedError(msg)
419
+
420
+ def __iter__(self) -> Iterator[DT]:
421
+ """Returns an iterator over the first dimension of the array.
422
+
423
+ This is not mandated by the array API standard, but numpy, torch and jax all support it.
424
+ """
425
+ msg = f"{type(self).__name__} does not support iteration."
426
+ raise NotImplementedError(msg)
427
+
428
+ @property
429
+ @abstractmethod
430
+ def flags(self) -> ArrayFlagsLike:
431
+ """The flags of the array."""
432
+
433
+ @override
434
+ def to_device(self, device: Any, /, *, stream: Any = None) -> Self:
435
+ """Move the array to a device."""
436
+ return self
437
+
438
+
439
+ NumpyLikeImplementation.register(np.ndarray)
440
+
441
+
442
+ @flexdispatch
443
+ def to_numpy_like[DT](
444
+ array: object,
445
+ *,
446
+ dtype: DTypeLike | None = None,
447
+ copy: bool | None = None,
448
+ ) -> NumpyLike[Any] | np.ndarray:
449
+ """Convert an ArrayLike to a NumpyLike.
450
+
451
+ If possible, use the __array_like__ method to convert the array, otherwise use np.asanyarray.
452
+
453
+ Args:
454
+ array: The array to convert.
455
+ dtype: The desired data type of the output array.
456
+ copy: Whether to return a copy of the input array.
457
+
458
+ Returns:
459
+ The converted array.
460
+ """
461
+ return np.asanyarray(array, dtype=dtype, copy=copy)
462
+
463
+
464
+ @to_numpy_like.register(NumpyLikeConvertible)
465
+ def _to_numpy_like_convertible(
466
+ array: NumpyLikeConvertible,
467
+ *,
468
+ dtype: DTypeLike | None = None,
469
+ copy: bool | None = None,
470
+ ) -> NumpyLike[Any] | NDArray[Any]:
471
+ return array.__array_like__(dtype, copy=copy)