Это закончилось немного дольше, чем ожидалось, но вы можете написать свою собственную обертку, которая проверяет операции get, чтобы убедиться, что срезы не выходят за пределы (аргументы индексации, которые не являются срезами, уже проверяются NumPy).Я думаю, что я рассмотрел все случаи здесь (многоточие, np.newaxis
, отрицательные шаги ...), хотя все еще может быть некоторый неудачный угловой случай.
import numpy as np
# Wrapping function
def bounds_checked_slice(arr):
return SliceBoundsChecker(arr)
# Wrapper that checks that indexing slices are within bounds of the array
class SliceBoundsChecker:
def __init__(self, arr):
self._arr = np.asarray(arr)
def __getitem__(self, args):
# Slice bounds checking
self._check_slice_bounds(args)
return self._arr.__getitem__(args)
def __setitem__(self, args, value):
# Slice bounds checking
self._check_slice_bounds(args)
return self._arr.__setitem__(args, value)
# Check slices in the arguments are within bounds
def _check_slice_bounds(self, args):
if not isinstance(args, tuple):
args = (args,)
# Iterate through indexing arguments
arr_dim = 0
i_arg = 0
for i_arg, arg in enumerate(args):
if isinstance(arg, slice):
self._check_slice(arg, arr_dim)
arr_dim += 1
elif arg is Ellipsis:
break
elif arg is np.newaxis:
pass
else:
arr_dim += 1
# Go backwards from end after ellipsis if necessary
arr_dim = -1
for arg in args[:i_arg:-1]:
if isinstance(arg, slice):
self._check_slice(arg, arr_dim)
arr_dim -= 1
elif arg is Ellipsis:
raise IndexError("an index can only have a single ellipsis ('...')")
elif arg is np.newaxis:
pass
else:
arr_dim -= 1
# Check a single slice
def _check_slice(self, slice, axis):
size = self._arr.shape[axis]
start = slice.start
stop = slice.stop
step = slice.step if slice.step is not None else 1
if step == 0:
raise ValueError("slice step cannot be zero")
bad_slice = False
if start is not None:
start = start if start >= 0 else start + size
bad_slice |= start < 0 or start >= size
else:
start = 0 if step > 0 else size - 1
if stop is not None:
stop = stop if stop >= 0 else stop + size
bad_slice |= (stop < 0 or stop > size) if step > 0 else (stop < 0 or stop >= size)
else:
stop = size if step > 0 else -1
if bad_slice:
raise IndexError("slice {}:{}:{} is out of bounds for axis {} with size {}".format(
slice.start if slice.start is not None else '',
slice.stop if slice.stop is not None else '',
slice.step if slice.step is not None else '',
axis % self._arr.ndim, size))
Небольшая демонстрация:
import numpy as np
a = np.arange(24).reshape(4, 6)
print(bounds_checked_slice(a)[:2, 1:5])
# [[ 1 2 3 4]
# [ 7 8 9 10]]
bounds_checked_slice(a)[:2, 4:10]
# IndexError: slice 4:10: is out of bounds for axis 1 with size 6
Если вы хотите, вы можете даже сделать это подклассом ndarray , так что вы получите такое поведение по умолчанию, вместо того, чтобы каждый раз оборачивать массив.
Также обратите вниманиечто могут быть некоторые вариации относительно того, что вы можете считать «за пределами».Приведенный выше код учитывает, что выход хотя бы одного индекса за пределы размера выходит за пределы, что означает, что вы не можете взять пустой фрагмент с чем-то вроде arr[len(arr):]
.В принципе, вы можете редактировать код, если думаете о немного другом поведении.