Python列表/数组:禁用切片中的负索引环绕

Posted

技术标签:

【中文标题】Python列表/数组:禁用切片中的负索引环绕【英文标题】:Python lists/arrays: disable negative indexing wrap-around in slices 【发布时间】:2012-11-04 19:49:04 【问题描述】:

虽然我发现负数环绕(即A[-2] 索引倒数第二个元素)在许多情况下非常有用,但当它发生在切片内时,它通常更令人烦恼而不是有用的功能,而且我经常希望找到一种方法来禁用该特定行为。

下面是一个固定的 2D 示例,但我对其他数据结构和其他维度的数量也有过几次同样的不满。

import numpy as np
A = np.random.randint(0, 2, (5, 10))

def foo(i, j, r=2):
  '''sum of neighbours within r steps of A[i,j]'''
  return A[i-r:i+r+1, j-r:j+r+1].sum()

在上面的切片中,我希望切片的任何负数都被视为与None 相同,而不是包装到数组的另一端。

由于包装,上面其他很好的实现在边界条件下给出了不正确的结果,并且需要某种补丁,例如:

def ugly_foo(i, j, r=2):
  def thing(n):
    return None if n < 0 else n
  return A[thing(i-r):i+r+1, thing(j-r):j+r+1].sum()

我也尝试过对数组或列表进行零填充,但它仍然不优雅(需要相应地调整查找位置索引)并且效率低下(需要复制数组)。

我是否缺少一些标准技巧或优雅的解决方案来进行这样的切片?我注意到 python 和 numpy 已经很好地处理了您指定太大数字的情况 - 也就是说,如果索引大于数组的形状,它的行为与None 相同。

【问题讨论】:

max(n, 0) 是否可以替代您的thing(n) 没错,在这个例子中是等价的,但不是我想的那样 投反对票。你已经有了一个很好的解决方案。停止试图变得聪明和过于复杂的事情。亲吻 @SchighSchagh 您应该提供一些关于您的意见原因背后的详细信息,并将其发布在答案中。 downvotes 的礼仪是将它们用于草率、错误或写得不好的帖子,而没有显示任何研究工作。 【参考方案1】:

如果这只需要应用于一些特定的操作,simple & straightworward if index&gt;=0: do_something(array[i]) / if index&lt;0: raise IndexError 就可以了。

如果这需要更广泛地应用,它仍然是相同的逻辑,只是以这种或另一种方式包装。

【讨论】:

【参考方案2】:

我的猜测是,您必须围绕所需对象创建自己的子类包装器并重新实现 __getitem__() 以将否定键转换为 None,然后调用超类 __getitem__

注意,我的建议是继承现有的自定义类,而不是像 listdict 这样的内置函数。这只是围绕另一个类创建一个实用程序,而不是混淆list 类型的正常预期操作。这将是您希望在特定上下文中使用一段时间直到您的操作完成的东西。最好避免进行会使代码用户感到困惑的全局不同更改。

Datamodel

object.getitem(self,key) 呼吁实施评估 自我[键]。对于序列类型,接受的键应该是整数 和切片对象。注意否定的特殊解释 索引(如果类希望模拟序列类型)取决于 getitem() 方法。如果 key 的类型不合适,可能会引发 TypeError;如果在索引集之外的值 序列(在对负值进行任何特殊解释之后), 应该引发 IndexError。对于映射类型,如果缺少键(不是 在容器中),应该引发 KeyError。

您甚至可以创建一个简单地将实例作为 arg 的包装器,并在转换密钥时将所有 __getitem__() 调用推迟到该私有成员,以应对您不能或不想子类化的情况一个类型,而只是想要一个用于任何序列对象的实用程序包装器。

后一种建议的快速示例:

class NoWrap(object):

    def __init__(self, obj, default=None):
        self._obj = obj 
        self._default = default

    def __getitem__(self, key):
        if isinstance(key, int):
            if key < 0:
                return self._default

        return self._obj.__getitem__(key)

In [12]: x = range(-10,10)
In [13]: x_wrapped = NoWrap(x)
In [14]: print x_wrapped[5]
-5
In [15]: print x_wrapped[-1]
None 
In [16]: x_wrapped = NoWrap(x, 'FOO')
In [17]: print x_wrapped[-1]
FOO

【讨论】:

我宁愿扩展collections.MutableSequence以保证API一致性,并封装list 有趣,但它不适用于切片。不过,我可以看到如何概括这一点。 在这种情况下,并不是使用IntType 而不是isinstance 的具体原因。添加对切片的支持只需要再次检查isinstance(key, slice),然后将key 设置为您在查看旧的start / stop 属性后创建的新切片。 抱歉,这种对标准 Python 语法的骇人听闻如何比边界检查更简洁明了? @jdi: is IntType 是 Pythonic 错误,我的朋友 :) 如果您认为 is IntTypeisinstance(…, int) 一样 Pythonic,也许您应该提出一个问题?【参考方案3】:

我认为这还不足以证明新类和包装东西的合理性。 然后又是你的代码。

def foo(i, j, r=2):
  '''sum of neighbours within r steps of A[i,j]'''
  return A[i-r:abs(i+r+1), j-r:abs(j+r+1)].sum()   # ugly, but works?

(投反对票很有趣,所以我添加了更多选项)

我发现了一些非常出乎意料的事情(对我来说):__getslice__(i,j) 没有换行!相反,负索引会被忽略,所以:

lst[1:3] == lst.__getslice__(1,3)

lst[-3:-1] == 2 next to last itemslst.__getslice__(-3,-1) == []

最后:

lst[-2:1] == [],但lst.__getslice__(-2,1) == lst[0:1]

令人惊讶、有趣且完全没用。

【讨论】:

__getslice__ 的行为很有趣。不幸的是,它对二维数组用例没有用,它似乎只能以这种方式切片整行。 是的,__getslice__() 没有 axis 的概念。只是为了好玩而添加,对您的问题无用。【参考方案4】:

虽然您可以子类化,例如list 正如 jdi 所建议的那样,Python 的切片行为不是任何人都希望你解决的问题。

更改它可能会导致其他使用您的代码的人在其行为不符合预期时感到非常头疼 - 而且他们可能需要一段时间才能查看您的子类的特殊方法才能看到到底发生了什么。

见:Action at a distance

【讨论】:

这是一个很好的考虑点。虽然只是为了澄清,但我并不是建议像 list 这样子类化内置函数。仅对 OP 已经使用的类型进行子类化......或创建一个实用程序类来包装效果。 我并没有考虑过多地覆盖它,理想情况下我会保留现有的切片,但可能会创建一个新的语法......例如A[-1:3] 用于通常的方式,类似A[-1;3] 用于非包装切片......如果这不是完全不可能的,不知道如何在 python 中做到这一点而不会产生语法错误 是的,我明白了......但是一个切片被普遍理解为具有特定含义,所以改变它是自找麻烦,不管你怎么做IMO。也许像obj.abs_slice(i, j) 这样的方法是一个可以接受的妥协...... 我应该补充一点,虽然我经常使用A[-1] 来获取最后一个元素,但我认为我曾经想要或不需要@987654328 @ 是空列表而不是返回 [0, 1, 2] @wim:创建新语法并不是你应该用 python 真正考虑的事情。最好只创建一个新方法,用给定的参数做你想做的事情。

以上是关于Python列表/数组:禁用切片中的负索引环绕的主要内容,如果未能解决你的问题,请参考以下文章

55个案例:吃透Python列表的索引和切片

具有任意索引的 Python 列表切片

Python中的负索引[重复]

乐哥学AI_Python:Numpy索引,切片,常用函数

是否可以使用 python 3 对 h5py 中的键进行切片而不形成列表?

为啥python的列表切片不会产生索引越界错误? [复制]