3

考虑以下可以正常执行的 Python + NumPy 代码:

a = np.array((1, 2, 3))

a[13:17] = 23

使用超出数组限制的切片会截断切片,如果开始和停止超出限制,甚至会返回一个空视图。分配给这样一个切片只会丢弃输入。

在我的用例中,索引以非平凡的方式计算,用于操作数组的选定部分。上述行为意味着如果索引计算错误,我可能会默默地跳过部分操作。这可能很难检测到,并且可能导致“几乎正确”的结果,即最严重的编程错误。

出于这个原因,我希望对切片进行严格检查,以便在数组边界之外的开始或停止触发错误。有没有办法在 NumPy 中启用它?

作为附加信息,数组很大并且操作执行得非常频繁,即不应该有性能损失。此外,数组通常是多维的,包括多维切片。

4

3 回答 3

2

您可以np.put_along_axis改用它,这似乎符合您的需求:

>>> a = np.array((1, 2, 3))
>>> np.put_along_axis(a, indices=np.arange(13, 17), axis=0, values=23)

以上将引发以下错误:

IndexError:索引13超出了轴0的大小3

参数values可以是标量值或另一个 NumPy 数组。

或者更短的形式:

>>> np.put_along_axis(a, np.r_[13:17], 23, 0)

编辑:或者np.put有一个mode='raise'选项(默认设置):

np.put(a, ind, v, mode='raise')

  • a: ndarray - 目标数组。

  • ind: array_like - 目标索引,解释为整数。

  • varray_like - 放置在目标索引处的值。[...]

  • mode: {'raise', 'wrap', 'clip'} 可选- 指定越界索引的行为方式。

    • 'raise' - 引发错误(默认)
    • 'wrap' - 环绕
    • 'clip' - 剪辑到范围

默认行为将是:

>>> np.put(a, np.r_[13:17], 23)

IndexError:索引13超出了轴0的大小3

而与mode='clip',它保持沉默:

 >>> np.put(a, np.r_[13:17], 23, mode='clip')
于 2021-09-03T15:27:38.337 回答
1

实现您想要的行为的一种方法是使用范围而不是切片:

a = np.array((1, 2, 3))
a[np.arange(13, 17)] = 23

我认为 NumPy 在这里的行为与纯 Python 列表的行为是一致的,应该是可以预料的。与变通方法相比,显式添​​加断言可能会更好地提高代码的可读性:

index_1, index_2 = ... # a complex computation
assert index_1 < index_2 and index_2 < a.shape[0]
a[index_1:index_2] = 23
于 2021-09-03T16:02:58.133 回答
1

根据您的索引的复杂程度(阅读:在切片后预测形状的背面有多少痛苦),您可能希望直接计算预期的形​​状,然后计算reshape它。如果实际切片数组的大小不匹配,则会引发错误。开销很小:

import numpy as np
from timeit import timeit


def use_reshape(a,idx,val):
    expected_shape = ((s.stop-s.start-1)//(s.step or 1) + 1 if isinstance(s,slice) else 1 for s in idx)
    a[idx].reshape(*expected_shape)[...] = val

def no_check(a,idx,val):
    a[idx] = val
    
val = 23
idx = np.s_[13:1000:2,14:20]
for f in (no_check,use_reshape):
    a = np.zeros((1000,1000))
    print(f.__name__)
    print(timeit(lambda:f(a,idx,val),number=1000),'ms')
    assert (a[idx] == val).all()
    
# check it works
print("\nThis should raise an exception:\n")
use_reshape(a,np.s_[1000:1001,10],0)

请注意,这是概念验证代码。为了安全起见,您必须检查意外的索引类型、匹配的维度数量,重要的是,检查选择单个元素的索引。

无论如何运行它:

no_check
0.004587646995787509 ms
use_reshape
0.006306983006652445 ms

This should raise an exception:

Traceback (most recent call last):
  File "check.py", line 22, in <module>
    use_reshape(a,np.s_[1000:1001,10],0)
  File "check.py", line 7, in use_reshape
    a[idx].reshape(*expected_shape)[...] = val
ValueError: cannot reshape array of size 0 into shape (1,1)
于 2021-09-03T16:36:58.260 回答