Python Numpy索引-关于奇怪行为/不一致性的问题

Python Numpy索引-关于奇怪行为/不一致性的问题,python,numpy,Python,Numpy,这是一个基于我今天早些时候的一个问题的知识主题。我亲眼目睹了努比行为中的一些奇怪的矛盾 首先,如果运行此代码: A = ones((10,4)) view = A[:,1] view.fill(7) A 这将把第2列改为所有7列,因为数组从0开始索引,而切片只是同一矩阵的视图。太棒了,这正是我想要的 现在,如果运行以下命令: A = ones((10,4)) view = A[:,1:2] view.fill(7) A A = ones((10,4)) view = A[:,(1,2)] v

这是一个基于我今天早些时候的一个问题的知识主题。我亲眼目睹了努比行为中的一些奇怪的矛盾

首先,如果运行此代码:

A = ones((10,4))
view = A[:,1]
view.fill(7)
A
这将把第2列改为所有7列,因为数组从0开始索引,而切片只是同一矩阵的视图。太棒了,这正是我想要的

现在,如果运行以下命令:

A = ones((10,4))
view = A[:,1:2]
view.fill(7)
A
A = ones((10,4))
view = A[:,(1,2)]
view.fill(7)
A
它将具有与第一个示例相同的效果。为什么a:b指定从a到b-1的列?这在语言中有具体的原因吗?如果我输入1:3,那应该是第1、2和3列,而不是第1和第2列

最后,如果您运行以下命令:

A = ones((10,4))
view = A[:,1:2]
view.fill(7)
A
A = ones((10,4))
view = A[:,(1,2)]
view.fill(7)
A
A上没有副作用。看起来,如果使用元组创建视图,它无法正确地在原始矩阵上传播任何其他副作用。有什么见解吗

为什么a:b指定从a到b-1的列

这是一个Python约定。普通列表也是如此,
range(a,b)
将返回一个列表,其中包含
a
b-1
,但不包括
b
。此约定的好处是,通过
a:b
进行切片,其中
a
b
是数字,将返回
b-a
元素/行/列,而不是更复杂的
a-b+1

看起来,如果使用元组创建视图,它就无法正确地在原始矩阵上传播任何进一步的副作用

这是一种Numpy特性,因为它只能创建基于切片的视图;这些可以有效地实现,而基于元组的切片则不能。您可以使用以下代码段模拟此行为,该代码段显示了Python索引语法在后台的作用:

class FakeArray(object):
    def __getitem__(self, idx):
        return "You can't change the original FakeArray through me"
    def __setitem__(self, idx, val):
        print("We could set elements %r to %r here" % (idx, val))
现在试试

>>> A = FakeArray()
>>> A[1:2]
"You can't change the original FakeArray through me"
>>> A[1:2] = 'ham'
We could set elements slice(1, 2, None) to 'ham'

所以
A[1:2]
A的缩写。uuu getitem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuu(切片(1,2,无))
A[1:2]='ham'
A的缩写。uuu setitem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。因为实际上涉及到两种不同的方法,所以切片的行为可能非常不同,这取决于它是否是赋值语句的一部分。在Numpy的例子中,这种差异与
切片
元组
对象之间存在微妙的相互作用。

半开区间的使用并不特定于Numpy,而是在所有Python中使用。列表滑动的工作方式相同,
range()
函数也是如此

与闭合间隔相比,使用半开放间隔有几个优点:

  • 对于半开放区间,可以表示空的切片和范围,这对于闭合区间来说是困难的。这通常是有用的

  • 半开放区间
    [a,b)
    的长度由
    b-a
    简单给出,对于封闭区间
    [a,b]

  • 相邻的间隔更容易表达。假设我们有一个算法,在列表
    a
    中的
    k
    元素块上运行。比较Python中的实现

    for i in range(0, len(a), k):
        frobnicate(a[i:i + k])
    
    对于封闭区间的实施情况:

    for i in range(0, len(a) - 1, k):
        frobnicate(a[i:i + k - 1])
    
    代码中将出现大量的
    -1
    ,如果第一个间隔的右值等于第二个间隔的左值,则两个间隔相邻的属性将丢失

  • 为什么a:b指定从a到b-1的列

    这就是所有Python的工作方式,也是许多编程的传统。它允许许多事情的简单计算。例如,它允许切片长度
    x[a:a+n]
    be
    n
    并允许
    x[:n]
    x[n:]
    x
    分成两部分。你会习惯它,从长远来看,大多数程序员都喜欢它

    看起来,如果使用元组创建视图,它就无法正确地在原始矩阵上传播任何进一步的副作用


    当你做
    A[:,(1,2)]
    时,你没有视图,只有一个新的数组。当你只做切片时,比如
    A[:,1:3]
    ,你的数组的条带仍然有连续的内存,所以有视图是有意义的。当你通过使用iterable(想象一下,为了更好地理解你使用的
    (0,2)
    ),拥有像bahvior这样的视图将是低效和尴尬的。

    Edsger Dijkstra的一本好书:约定是有道理的,但是,我想,如果我想在视图中获取一列及其邻居,我需要执行col:col+2。最后的编辑在元组问题上更有意义。这是一个令人讨厌的白痴ncrasy,因为有时用元组来选择我需要的列更有用。@Bjornen:这是一种折衷。如果Python使用闭合区间约定,则必须用
    I:len(x)-1而不是
    I:len(x)来抓取
    I
    。这是一种Numpy的特质。如果你知道像
    ndarray
    这样的类型是如何工作的,那么这并不是那么独特或微妙。@MikeGraham:我想是的,但是
    np.ndarray
    及其视图行为中涉及的各种设计选择对普通人来说并不明显(甚至是相当高级的)用户。事实上,相同的语法给出了一个切片的视图,但一个元组的新数组可以正确地称为一种特殊性,IMHO。只是为了记录,你不是用元组索引
    ndarray
    ,而是用
    ndarray
    索引一个
    ndarray,“选择元组中的所有序列和标量都转换为intp索引数组。”我同意这种“特殊性”在您第一次遇到它时可能会有点混乱,但另一种选择是禁用“奇特索引”,并强制人们使用
    arra