Slicing in __getitem__ implementieren

112

Ich versuche, die Slice-Funktionalität für eine Klasse zu implementieren, die eine Vektordarstellung erstellt.

Ich habe diesen Code bis jetzt, von dem ich glaube, dass er das Slice richtig implementiert, aber wenn ich einen Aufruf wie v[4]v einen Vektorpython mache, wird ein Fehler zurückgegeben, dass nicht genügend Parameter vorhanden sind. Ich versuche also herauszufinden, wie ich die getitemspezielle Methode in meiner Klasse definieren kann, um sowohl einfache Indizes als auch Slicing zu verarbeiten.

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]
Nikotin
quelle

Antworten:

118

Die __getitem__()Methode erhält ein sliceObjekt, wenn das Objekt in Scheiben geschnitten wird. Einfach auf den start, stopund die stepMitglieder des sliceObjekts, um die Komponenten für die Scheibe zu bekommen.

>>> 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
quelle
10
Hinweis: Zum Erweitern von integrierten Typen wie Liste oder Tupel müssen Sie Implementierungen __getslice__für Python 2.X-Versionen implementieren . siehe docs.python.org/2/reference/datamodel.html#object.__getslice__
gregorySalvan
@gregorySalvan: Wiederholt sich das Kompatibilitätsbeispiel unter diesem Abschnitt nicht einfach?
Eric
3
@Eric: Nein, weil das Vorhandensein des zweiten Doppelpunkts umgangen wird __get/set/delslice__. Es ist jedoch ziemlich subtil.
user2357112 unterstützt Monica
@ user2357112: Wow, habe diesen zweiten Doppelpunkt komplett verpasst - danke!
Eric
@alancalvitti IIRC, das ist für das Erstellen von Klassen neuen Stils in Python 2.
wjandrea
64

Ich habe eine "synthetische" Liste (eine, in der die Daten größer sind, als Sie im Speicher erstellen möchten), und meine __getitem__sieht folgendermaßen aus:

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

Das Slice gibt nicht den gleichen Typ zurück, was ein Nein-Nein ist, aber es funktioniert für mich.

Walter Nissen
quelle
1
Sollte nicht sein, wenn Schlüssel> = len (Selbst) ist, wenn Schlüssel <0 oder Schlüssel> = len (Selbst)? Was ist, wenn ein Schlüssel <-len (self) übergeben wird?
Estan
20

Wie definiere ich die getitem-Klasse, um sowohl einfache Indizes als auch Slicing zu behandeln?

Slice - Objekte automatisch erstellt wird , wenn Sie einen Doppelpunkt in der Index - Notation verwenden - und das ist das, was passiert ist __getitem__. Verwenden isinstanceSie diese Option , um zu überprüfen, ob Sie ein Slice-Objekt haben:

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)

Angenommen, wir haben ein Bereichsobjekt verwendet, möchten jedoch, dass Slices Listen anstelle neuer Bereichsobjekte zurückgeben (wie dies der Fall ist):

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

Wir können den Bereich aufgrund interner Einschränkungen nicht unterordnen, aber wir können ihn delegieren:

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)

Wir haben kein perfekt austauschbares Range-Objekt, aber es ist ziemlich nah:

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

Um die Slice-Notation besser zu verstehen, sehen Sie hier die Verwendung von 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, sei dir bewusst:

In Python 2 gibt es eine veraltete Methode, die Sie möglicherweise überschreiben müssen, wenn Sie einige integrierte Typen unterklassifizieren.

Aus der Dokumentation zum Datenmodell :

object.__getslice__(self, i, j)

Veraltet seit Version 2.0: Unterstützt Slice-Objekte als Parameter für die __getitem__()Methode. (Die in CPython integrierten Typen werden derzeit jedoch noch implementiert __getslice__(). Daher müssen Sie sie bei der Implementierung des Slicing in abgeleiteten Klassen überschreiben.)

Dies ist in Python 3 weg.

Aaron Hall
quelle
7

Um Aarons Antwort zu erweitern numpy, können Sie beispielsweise mehrdimensionale Schnitte durchführen, indem Sie prüfen, ob givenes sich um Folgendes handelt 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]

`` `

Ausgabe:

('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))
Eric Cousineau
quelle
Als kleines Follow-up finden Sie hier ein Beispiel für die Verwendung dieser Zuordnung zwischen der MATLAB-Indizierung und der NumPy-Indizierung (die derzeit in MATLAB R2016b nicht unterstützt wird) mit einer beispielhaften Verwendung .
Eric Cousineau