Implementando o fatiamento em __getitem__


112

Estou tentando implementar a funcionalidade de fatia para uma classe que estou criando que cria uma representação vetorial.

Eu tenho esse código até agora, que acredito que implementará corretamente a fatia, mas sempre que faço uma chamada como v[4]onde v é um vetor, o python retorna um erro sobre não ter parâmetros suficientes. Portanto, estou tentando descobrir como definir o getitemmétodo especial em minha classe para lidar com índices simples e fatiamento.

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]

Respostas:


118

O __getitem__()método receberá um sliceobjeto quando o objeto for fatiado. Basta olhar para os start, stope stepos membros do sliceobjeto a fim de obter os componentes para a fatia.

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

10
Nota: para estender tipos embutidos como lista ou tupla, você deve implementar __getslice__para versões 2.X do python. consulte docs.python.org/2/reference/datamodel.html#object.__getslice__
— gregorySalvan

@gregorySalvan: Esse exemplo de compatibilidade abaixo dessa seção não é apenas recorrente?
— Eric

3
@Eric: Não, porque a presença do segundo cólon ignora __get/set/delslice__. É muito sutil, no entanto.
— user2357112 suporta Monica

@ user2357112: Uau, perdi completamente o segundo dois pontos - obrigado!
— Eric

@alancalvitti IIRC, que serve para criar classes de novo estilo em Python 2.
— wjandrea

64

Eu tenho uma lista "sintética" (uma em que os dados são maiores do que você gostaria de criar na memória) e minha __getitem__aparência é assim:

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."

A fatia não retorna o mesmo tipo, o que é um não, mas funciona para mim.


1
Não deveria if key> = len (self) ser if key <0 ou key> = len (self)? E se uma chave <-len (self) for passada?
— estan

20

Como definir a classe getitem para lidar com índices simples e fatiamento?

Objetos de fatia são criados automaticamente quando você usa dois-pontos na notação de subscrito - e é para isso que é passado __getitem__. Use isinstancepara verificar se você tem um objeto de fatia:

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)

Digamos que estivéssemos usando um objeto de intervalo, mas queremos que as fatias retornem listas em vez de novos objetos de intervalo (como acontece):

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

Não podemos subclassificar o intervalo devido a limitações internas, mas podemos delegar a ele:

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)

Não temos um objeto Range perfeitamente substituível, mas é bastante próximo:

>>> r[1:3]
[1, 2]
>>> r[1]
1
>>> 2 in r
True
>>> r.count(3)
1

Para entender melhor a notação de fatia, aqui está um exemplo de uso de 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, esteja ciente:

No Python 2, há um método obsoleto que você pode precisar substituir ao criar subclasses de alguns tipos integrados.

Da documentação do modelo de dados :

object.__getslice__(self, i, j)

Obsoleto desde a versão 2.0: Suporta objetos de fatia como parâmetros do __getitem__()método. (No entanto, os tipos integrados no CPython atualmente ainda implementam __getslice__(). Portanto, você deve substituí-los em classes derivadas ao implementar o fatiamento.)

Isso acabou no Python 3.


7

Para estender a resposta de Aaron, para coisas como numpy, você pode fazer o corte multi-dimensional, verificando para ver se givené um 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]

`` `

Resultado:

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

Como um pequeno acompanhamento, aqui está um exemplo de como empregar isso para mapear entre a indexação MATLAB e a indexação NumPy (que atualmente não é suportada no MATLAB R2016b), com um exemplo de uso dela.
— Eric Cousineau

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.