Implementando el corte en __getitem__


112

Estoy tratando de implementar la funcionalidad de corte para una clase que estoy creando y que crea una representación vectorial.

Tengo este código hasta ahora, que creo que implementará correctamente el segmento, pero cada vez que hago una llamada como v[4]donde v es un vector, python devuelve un error sobre no tener suficientes parámetros. Así que estoy tratando de averiguar cómo definir el getitemmétodo especial en mi clase para manejar tanto índices simples como cortes.

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]

Respuestas:


118

El __getitem__()método recibirá un sliceobjeto cuando se corte el objeto. Basta con mirar las start, stopy steplos miembros del sliceobjeto con el fin de obtener los componentes de la división.

>>> 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 extender los tipos incorporados como lista o tupla, debe implementar __getslice__para las versiones de python 2.X. ver docs.python.org/2/reference/datamodel.html#object.__getslice__
gregorySalvan

@gregorySalvan: ¿No es recurrente ese ejemplo de compatibilidad debajo de esa sección?
Eric

3
@Eric: No, porque la presencia del segundo colon pasa por alto __get/set/delslice__. Sin embargo, es bastante sutil.
user2357112 apoya a Monica

@ user2357112: Vaya, me perdí por completo los dos puntos.
Eric

@alancalvitti IIRC, eso es para crear clases de nuevo estilo en Python 2.
wjandrea

64

Tengo una lista "sintética" (una en la que los datos son más grandes de lo que le gustaría crear en la memoria) y mi __getitem__aspecto es el siguiente:

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

El segmento no devuelve el mismo tipo, lo cual es un no-no, pero funciona para mí.


1
¿No debería if key> = len (self) ser if key <0 o key> = len (self)? ¿Qué pasa si se pasa una clave <-len (self)?
estan

20

¿Cómo definir la clase getitem para manejar índices simples y segmentaciones?

Los objetos Slice se crean automáticamente cuando usa dos puntos en la notación de subíndice, y eso es a lo que se le pasa __getitem__. Úselo isinstancepara verificar si tiene un objeto de corte:

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 estamos usando un objeto de rango, pero queremos que los cortes devuelvan listas en lugar de nuevos objetos de rango (como lo hace):

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

No podemos subclasificar el rango debido a limitaciones internas, pero podemos delegar en él:

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)

No tenemos un objeto Range perfectamente reemplazable, pero está bastante cerca:

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

Para comprender mejor la notación de corte, aquí hay un ejemplo 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, tenga en cuenta:

En Python 2, hay un método obsoleto que es posible que deba anular al subclasificar algunos tipos integrados.

De la documentación del modelo de datos :

object.__getslice__(self, i, j)

En desuso desde la versión 2.0: admite objetos de corte como parámetros del __getitem__()método. (Sin embargo, los tipos integrados en CPython todavía se implementan __getslice__(). Por lo tanto, debe anularlos en las clases derivadas al implementar la división).

Esto desapareció en Python 3.


7

Para extender la respuesta de Aaron, para cosas como numpy, puede hacer cortes multidimensionales al verificar si givenes un 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]

''

Salida:

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

Como seguimiento menor, aquí hay un ejemplo de cómo emplear esto para mapear entre la indexación de MATLAB y la indexación de NumPy (que actualmente no es compatible con MATLAB R2016b), con un ejemplo de uso .
Eric Cousineau

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.