Python lists/arrays: disable negative indexing wrap

While I find the negative number wraparound (ie A[-2] indexing the second-to-last element) extremely useful in many cases, when it happens inside a slice it is usually more of an annoyance than a helpful feature, and I often wish for a way to disable that particular behaviour.

Here is a canned 2D example below, but I have had the same peeve a few times with other data structures and in other numbers of dimensions.

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()

In the slice above I would rather that any negative number to the slice would be treated the same as None is, rather than wrapping to the other end of the array.

Because of the wrapping, the otherwise nice implementation above gives incorrect results at boundary conditions and requires some sort of patch like:

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()

I have also tried zero-padding the array or list, but it is still inelegant (requires adjusting the lookup locations indices accordingly) and inefficient (requires copying the array).

Am I missing some standard trick or elegant solution for slicing like this? I noticed that python and numpy already handle the case where you specify too large a number nicely - that is, if the index is greater than the shape of the array it behaves the same as if it were None .


My guess is that you would have to create your own subclass wrapper around the desired objects and re-implement __getitem__() to convert negative keys to None , and then call the superclass __getitem__

Note, what I am suggesting is to subclass existing custom classes, but NOT builtins like list or dict . This is simply to make a utility around another class, not to confuse the normal expected operations of a list type. It would be something you would want to use within a certain context for a period of time until your operations are complete. It is best to avoid making a globally different change that will confuse users of your code.

Datamodel

object. getitem (self, key)
Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the getitem () method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.

You could even create a wrapper that simply takes an instance as an arg, and just defers all __getitem__() calls to that private member, while converting the key, for cases where you can't or don't want to subclass a type, and instead just want a utility wrapper for any sequence object.

Quick example of the latter suggestion:

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

While you could subclass eg list as suggested by jdi, Python's slicing behaviour is not something anyone's going to expect you to muck about with.

Changing it is likely to lead to some serious head-scratching by other people working with your code when it doesn't behave as expected - and it could take a while before they go looking at the special methods of your subclass to see what's actually going on.

See: Action at a distance


I think this isn't ugly enough to justify new classes and wrapping things. Then again it's your code.

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 is fun, so I've added some more options)

I found out something quite unexpected (for me): The __getslice__(i,j) does not wrap! Instead, negative indices are just ignored, so:

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

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

and finally:

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

Surprising, interesting, and completely useless.

链接地址: http://www.djcxy.com/p/26754.html

上一篇: 切片/跨度由变量?

下一篇: Python列表/数组:禁用负向索引换行