在 __getitem__ 中实现切片

140

我正在尝试为我创建的向量表示类实现切片功能。

我已经编写了以下代码,我相信它会正确地实现切片,但无论何时我执行类似于v[4]这样的操作,其中v是一个向量,Python都会引发关于参数不足的错误。因此,我正在尝试弄清楚如何定义我的类中的__getitem__特殊方法来处理普通索引和切片。

def __getitem__(self, start, stop, step):
    index = start
    if stop == None:
        end = start + 1
    else:
        end = stop
    if step == None:
        stride = 1
    else:
        stride = step
    return self.__data[index:end:stride]
5个回答

150
__getitem__()方法在对象被切片时会接收到一个slice对象。只需查看slice对象的startstopstep成员,以获取切片的组件即可。
>>> class C(object):
...   def __getitem__(self, val):
...     print val
... 
>>> c = C()
>>> c[3]
3
>>> c[3:4]
slice(3, 4, None)
>>> c[3:4:-2]
slice(3, 4, -2)
>>> c[():1j:'a']
slice((), 1j, 'a')

11
注意:如果要扩展内置类型,如列表或元组,在Python 2.X版本中必须实现__getslice__。请参阅https://docs.python.org/2/reference/datamodel.html#object.__getslice__。 - gregorySalvan
@gregorySalvan:下面那个兼容性示例不是只是递归吗? - Eric
3
不行,因为存在第二个冒号会绕过__get/set/delslice__。虽然这很微妙。 - user2357112
@user2357112:哇,完全错过了第二个冒号 - 谢谢! - Eric
如果我没记错的话,这是用于在Python 2中创建新式类的。 - wjandrea
请注意,__getitem__可以接收一个slice,但也可以像c[0]一样接收一个int。请参阅https://docs.python.org/3/reference/datamodel.html#object.__getitem__。 - Luca

87

我有一个“合成”列表(其中数据比您想要在内存中创建的要大),我的__getitem__看起来是这样的:

def __getitem__(self, key):
    if isinstance(key, slice):
        # Get the start, stop, and step from the slice
        return [self[ii] for ii in xrange(*key.indices(len(self)))]
    elif isinstance(key, int):
        if key < 0: # Handle negative indices
            key += len(self)
        if key < 0 or key >= len(self):
            raise IndexError, "The index (%d) is out of range." % key
        return self.getData(key) # Get the data from elsewhere
    else:
        raise TypeError, "Invalid argument type."

切片返回的类型不同,这是不好的,但它对我有用。


1
应该将 if key >= len(self) 改为 if key < 0 or key >= len(self),这样才更加准确。如果传递一个小于 -len(self) 的 key 值会怎样呢? - estan

35

如何定义getitem类以处理普通索引和切片?

当您在下标符号中使用冒号时,会自动创建Slice对象,并将其传递给 __getitem__ 。使用 isinstance 检查是否存在Slice对象:

from __future__ import print_function

class Sliceable(object):
    def __getitem__(self, subscript):
        if isinstance(subscript, slice):
            # do your handling for a slice object:
            print(subscript.start, subscript.stop, subscript.step)
        else:
            # Do your handling for a plain index
            print(subscript)

假设我们正在使用一个range对象,但我们想要切片返回列表而不是新的range对象(就像它现在所做的那样):

>>> range(1,100, 4)[::-1]
range(97, -3, -4)

由于内部限制,我们无法对范围进行子类化,但是我们可以将其委托:

class Range:
    """like builtin range, but when sliced gives a list"""
    __slots__ = "_range"
    def __init__(self, *args):
        self._range = range(*args) # takes no keyword arguments.
    def __getattr__(self, name):
        return getattr(self._range, name)
    def __getitem__(self, subscript):
        result = self._range.__getitem__(subscript)
        if isinstance(subscript, slice):
            return list(result)
        else:
            return result

r = Range(100)

我们没有完全可替代的Range对象,但它相当接近:

>>> r[1:3]
[1, 2]
>>> r[1]
1
>>> 2 in r
True
>>> r.count(3)
1
为了更好地理解切片符号,这里是Sliceable的示例用法:
>>> sliceme = Sliceable()
>>> sliceme[1]
1
>>> sliceme[2]
2
>>> sliceme[:]
None None None
>>> sliceme[1:]
1 None None
>>> sliceme[1:2]
1 2 None
>>> sliceme[1:2:3]
1 2 3
>>> sliceme[:2:3]
None 2 3
>>> sliceme[::3]
None None 3
>>> sliceme[::]
None None None
>>> sliceme[:]
None None None

Python 2注意:

在Python 2中,有一个已经不再推荐使用的方法,当子类化某些内置类型时可能需要覆盖它。

根据 datamodel文档:

object.__getslice__(self, i, j)

自2.0版本起不推荐使用: 将切片对象作为参数传递给__getitem__()方法。(然而,CPython中的内置类型仍然实现__getslice__()。因此,在实现切片时必须在派生类中覆盖它。)

这在Python 3中已经不存在了。


当我们处理普通索引时,我们不能调用self[index],因为它会进入递归,那么如何访问正确的元素? - Jitin
1
如果你想使用父类已经实现的方法,可以使用 super()。请参考 https://dev59.com/gHVC5IYBdhLWcg3wpi98#33469090。 - Russia Must Remove Putin

9

补充Aaron的回答,对于像numpy这样的东西,您可以通过检查given是否为tuple来进行多维切片:

class Sliceable(object):
    def __getitem__(self, given):
        if isinstance(given, slice):
            # do your handling for a slice object:
            print("slice", given.start, given.stop, given.step)
        elif isinstance(given, tuple):
            print("multidim", given)
        else:
            # Do your handling for a plain index
            print("plain", given)

sliceme = Sliceable()
sliceme[1]
sliceme[::]
sliceme[1:, ::2]

```

输出:

('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))

作为一个小的后续,这里有一个例子,展示了如何使用它来映射MATLAB索引和NumPy索引(在MATLAB R2016b中目前不支持),并提供了一个使用示例。具体请参见:https://github.com/EricCousineau-TRI/repro/blob/62151af/bindings/mlmodule/NumPyProxy.m#L83 和 https://github.com/EricCousineau-TRI/repro/blob/62151af/bindings/mlmodule/test/example_py_slice.m#L43。 - Eric Cousineau

8

正确的方法是让 __getitem__ 接收一个参数,可以是数字或者是 slice 对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接