Wdrażanie krojenia w __getitem__

112

Próbuję zaimplementować funkcjonalność wycinka dla klasy, którą tworzę, która tworzy reprezentację wektorową.

Do tej pory mam ten kod, który, jak sądzę, prawidłowo zaimplementuje wycinek, ale za każdym razem, gdy wykonuję wywołanie, takie jak v[4]gdzie v jest wektorem, Python zwraca błąd o braku wystarczających parametrów. Więc próbuję dowiedzieć się, jak zdefiniować getitemspecjalną metodę w mojej klasie do obsługi zarówno zwykłych indeksów, jak i wycinania.

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]
nikotyna
źródło

Odpowiedzi:

118

__getitem__()Metoda otrzyma sliceprzedmiot, gdy obiekt jest w plasterkach. Wystarczy spojrzeć na start, stopi stepczłonków sliceobiektu w celu uzyskania komponenty dla wycinka.

>>> 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
źródło
10
Uwaga: aby rozszerzyć typy wbudowane, takie jak lista lub krotka, musisz zaimplementować __getslice__dla wersji Python 2.X. zobacz docs.python.org/2/reference/datamodel.html#object.__getslice__
gregorySalvan
@gregorySalvan: Czy ten przykład zgodności poniżej tej sekcji nie powtarza się po prostu?
Eric
3
@Eric: Nie, ponieważ obecność drugiego dwukropka omija __get/set/delslice__. Jest to jednak dość subtelne.
user2357112 obsługuje Monikę
@ user2357112: Wow, całkowicie przegapiłem drugi dwukropek - dzięki!
Eric
@alancalvitti IIRC, służy do tworzenia klas w nowym stylu w Pythonie 2.
wjandrea
64

Mam listę „syntetyczną” (taką, w której dane są większe niż chciałbyś utworzyć w pamięci) i __getitem__wygląda to tak:

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

Kawałek nie zwraca tego samego typu, czyli nie, nie, ale dla mnie działa.

Walter Nissen
źródło
1
Czy nie powinno, jeśli key> = len (self) powinno być if key <0 or key> = len (self)? Co się stanie, jeśli klucz <-len (self) zostanie przekazany?
estan
20

Jak zdefiniować klasę getitem do obsługi zarówno zwykłych indeksów, jak i fragmentacji?

Obiekty plasterków są tworzone automatycznie, gdy używasz dwukropka w notacji indeksu dolnego - i to jest to, co jest przekazywane do __getitem__. Użyj, isinstanceaby sprawdzić, czy masz obiekt plasterka:

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)

Powiedzmy, że używaliśmy obiektu zakresu, ale chcemy, aby plasterki zwracały listy zamiast nowych obiektów zakresu (tak jak to się dzieje):

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

Nie możemy podklasować zakresu z powodu wewnętrznych ograniczeń, ale możemy delegować do niego:

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)

Nie mamy doskonale wymiennego obiektu Range, ale jest dość blisko:

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

Aby lepiej zrozumieć notację plasterków, oto przykład użycia 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, pamiętaj:

W Pythonie 2 istnieje przestarzała metoda, którą może być konieczne zastąpienie podczas tworzenia podklas niektórych typów wbudowanych.

Z dokumentacji modelu danych :

object.__getslice__(self, i, j)

Przestarzałe od wersji 2.0: obsługuj obiekty wycinka jako parametry __getitem__()metody. (Jednak typy wbudowane w CPythonie są obecnie nadal implementowane __getslice__(). Dlatego podczas implementowania wycinania trzeba zastąpić je w klasach pochodnych).

To zniknęło w Pythonie 3.

Aaron Hall
źródło
7

Aby rozszerzyć odpowiedź Aarona, na przykład numpymożesz wykonać wielowymiarowe cięcie, sprawdzając, czy givenjest 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]

`` ''

Wynik:

('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))
Eric Cousineau
źródło
Jako drobna kontynuacja, oto przykład zastosowania tego do mapowania między indeksowaniem MATLAB a indeksowaniem NumPy (które obecnie nie jest obsługiwane w MATLAB R2016b), z przykładowym użyciem .
Eric Cousineau