【问题标题】:Implementing slicing in __getitem__在 __getitem__ 中实现切片
【发布时间】:2011-02-25 14:00:09
【问题描述】:

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

到目前为止,我有这段代码,我相信它会正确地实现切片,但是每当我执行像 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]

【问题讨论】:

    标签: python slice python-datamodel


    【解决方案1】:

    当对象被切片时,__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')
    

    【讨论】:

    • 注意:要扩展内置类型,如列表或元组,您必须为 python 2.X 版本实现__getslice__。见docs.python.org/2/reference/datamodel.html#object.__getslice__
    • @gregorySalvan:该部分下方的兼容性示例不只是递归吗?
    • @Eric:不,因为第二个冒号的存在绕过了__get/set/delslice__。不过,这很微妙。
    • @user2357112:哇,完全错过了第二个冒号 - 谢谢!
    • @alancalvitti IIRC,用于在 Python 2 中创建新型类。
    【解决方案2】:

    我有一个“综合”列表(其中的数据比您想在内存中创建的要大),我的 __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."
    

    切片不返回相同的类型,这是一个禁忌,但它对我有用。

    【讨论】:

    • 如果 key >= len( self ) 不应该是 key = len( self ) 吗?如果传递了一个键
    【解决方案3】:

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

    当您在下标符号中使用冒号时,会自动创建切片对象 - that 是传递给 __getitem__ 的内容。使用isinstance 检查是否有切片对象:

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

    object.__getslice__(self, i, j)

    2.0 版后已弃用:支持切片对象作为__getitem__() 方法的参数。 (但是,CPython 中的内置类型目前仍然实现__getslice__()。因此,在实现切片时,您必须在派生类中覆盖它。)

    这在 Python 3 中消失了。

    【讨论】:

    • 当我们处理普通索引时,我们不能调用 self[index] 因为它会进入递归,你如何访问正确的元素?
    • 如果你想使用你已经使用的方法的父实现,使用super()。见stackoverflow.com/questions/222877/…
    【解决方案4】:

    为了扩展 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)))
    

    【讨论】:

    • 作为一个小的后续行动,这里 is an example 使用它在 MATLAB 索引和 NumPy 索引之间进行映射(MATLAB R2016b 目前不支持),其中的 example usage
    【解决方案5】:

    正确的做法是让__getitem__ 接受一个参数,可以是数字,也可以是切片对象。

    见:

    http://docs.python.org/library/functions.html#slice

    http://docs.python.org/reference/datamodel.html#object.__getitem__

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-30
      • 2013-07-19
      • 1970-01-01
      • 2019-08-31
      • 2012-10-11
      相关资源
      最近更新 更多