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

时间:2012-11-16 04:47:04

标签: python arrays list numpy slice

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

下面是一个固定的2D示例,但我与其他数据结构和其他维度的数量相同。

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

enter image description here

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一样。

4 个答案:

答案 0 :(得分:4)

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

注意,我建议的是子类化现有的自定义类,但不是像listdict这样的内置类。这只是围绕另一个类创建一个实用程序,而不是混淆list类型的正常预期操作。在您的操作完成之前,您可能希望在特定环境中使用一段时间。最好避免进行全局不同的更改,这会使代码的用户感到困惑。

Datamodel

  

对象。 getitem (自我,关键)
  被要求实施评估   自[关键]。对于序列类型,接受的键应为整数   和切片对象。注意负面的特殊解释   索引(如果类希望模拟序列类型)取决于    getitem ()方法。如果key是不合适的类型,则可能引发TypeError; if的索引集之外的值   序列(在负值的任何特殊解释之后),   应该引发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

答案 1 :(得分:4)

虽然您可以子类化,例如正如jdi所建议的list,Python的切片行为并不是任何人都希望你去做的事情。

当它的行为不符合预期时,更改它可能会导致其他人使用您的代码时出现严重的问题 - 在他们查看子类的特殊方法之前可能需要一段时间才能看到实际上是怎么回事。

请参阅:Action at a distance

答案 2 :(得分:1)

我认为这并不足以证明新课程和包装事物的合理性。 那么这又是你的代码。

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?

(Downvoting很有趣,所以我添加了更多选项)

我发现了一些非常意外的事情(对我而言):__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]

令人惊讶,有趣,完全没用。

答案 3 :(得分:0)

如果只需要在一些特定操作中应用,simple & straightworward if index>=0: do_something(array[i]) / if index<0: raise IndexError就可以。

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