Python lists/arrays: disable negative indexing wrap-around in slices

后端 未结 4 2051
不知归路
不知归路 2020-12-11 15:43

While I find the negative number wraparound (i.e. A[-2] indexing the second-to-last element) extremely useful in many cases, when it happens inside a slice it i

相关标签:
4条回答
  • 2020-12-11 16:03

    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
    
    0 讨论(0)
  • 2020-12-11 16:03

    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.

    0 讨论(0)
  • 2020-12-11 16:13

    While you could subclass e.g. 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

    0 讨论(0)
  • 2020-12-11 16:17

    If this only needs to apply in a few specific operations, a simple & straightworward if index>=0: do_something(array[i]) / if index<0: raise IndexError would do.

    If this needs to apply wider, it's still the same logic, just being wrapped in this manner or another.

    0 讨论(0)
提交回复
热议问题