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]
nicotina
fonte

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')
Ignacio Vazquez-Abrams
fonte
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.

Walter Nissen
fonte
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.

Aaron Hall
fonte
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)))
Eric Cousineau
fonte
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