Implementazione del taglio in __getitem__


112

Sto cercando di implementare la funzionalità slice per una classe che sto creando che crea una rappresentazione vettoriale.

Finora ho questo codice, che credo implementerà correttamente la slice, ma ogni volta che eseguo una chiamata come v[4]dove v è un vettore Python restituisce un errore di non avere abbastanza parametri. Quindi sto cercando di capire come definire il getitemmetodo speciale nella mia classe per gestire sia gli indici semplici che lo slicing.

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]

Risposte:


118

Il __getitem__()metodo riceverà un sliceoggetto quando l'oggetto viene tagliato. Basta guardare ai start, stopei stepmembri del sliceoggetto al fine di ottenere i componenti per la fetta.

>>> 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: per estendere i tipi incorporati come list o tuple è necessario implementare __getslice__per le versioni di python 2.X. vedi docs.python.org/2/reference/datamodel.html#object.__getslice__
gregorySalvan

@gregorySalvan: L'esempio di compatibilità sotto quella sezione non si ripete?
Eric

3
@ Eric: No, perché la presenza del secondo colon bypassa __get/set/delslice__. È piuttosto sottile, però.
user2357112 supporta Monica

@ user2357112: Wow, ho perso completamente quel secondo due punti - grazie!
Eric

@alancalvitti IIRC, per creare nuove classi in Python 2.
wjandrea

64

Ho un elenco "sintetico" (uno in cui i dati sono più grandi di quanto vorresti creare in memoria) e il mio __getitem__aspetto è questo:

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

La fetta non restituisce lo stesso tipo, che è un no-no, ma per me funziona.


1
If key> = len (self) non dovrebbe essere if key <0 o key> = len (self)? Cosa succede se viene passata una chiave <-len (self)?
estan

20

Come definire la classe getitem per gestire sia gli indici semplici che lo slicing?

Gli oggetti slice vengono creati automaticamente quando si utilizzano i due punti nella notazione del pedice - e questo è ciò a cui viene passato __getitem__. Utilizzare isinstanceper verificare se si dispone di un oggetto 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)

Supponiamo che stessimo usando un oggetto intervallo, ma vogliamo che le sezioni restituiscano elenchi invece di nuovi oggetti intervallo (come fa):

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

Non possiamo sottoclassare l'intervallo a causa di limitazioni interne, ma possiamo delegare ad esso:

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)

Non abbiamo un oggetto Range perfettamente sostituibile, ma è abbastanza vicino:

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

Per comprendere meglio la notazione delle sezioni, ecco un esempio di utilizzo di 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, attenzione:

In Python 2, esiste un metodo deprecato che potrebbe essere necessario sovrascrivere quando si sottoclasse alcuni tipi incorporati.

Dalla documentazione del modello di dati :

object.__getslice__(self, i, j)

Deprecato dalla versione 2.0: supporta gli oggetti slice come parametri del __getitem__()metodo. (Tuttavia, i tipi incorporati in CPython attualmente sono ancora implementati __getslice__(). Pertanto, è necessario sovrascriverlo nelle classi derivate quando si implementa lo slicing.)

Questo è andato in Python 3.


7

Per estendere la risposta di Aaron, per cose come numpy, puoi eseguire l'affettatura multidimensionale controllando per vedere se 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]

`` `

Produzione:

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

Come follow-up minore, ecco un esempio di utilizzo di questo per mappare tra l'indicizzazione MATLAB e l'indicizzazione NumPy (che attualmente non è supportata in MATLAB R2016b), con un esempio di utilizzo di esso.
Eric Cousineau

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.