Menerapkan pemotongan di __getitem__

112

Saya mencoba menerapkan fungsionalitas irisan untuk kelas yang saya buat yang membuat representasi vektor.

Saya memiliki kode ini sejauh ini, yang saya percaya akan mengimplementasikan slice dengan benar tetapi setiap kali saya melakukan panggilan seperti di v[4]mana v adalah vektor python mengembalikan kesalahan karena tidak memiliki cukup parameter. Jadi saya mencoba mencari cara untuk menentukan getitemmetode khusus di kelas saya untuk menangani indeks biasa dan pemotongan.

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
sumber

Jawaban:

118

The __getitem__()Metode akan menerima sliceobjek ketika objek diiris. Cukup melihat start, stopdan stepanggota dari sliceobjek untuk mendapatkan komponen untuk potongan.

>>> 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
sumber
10
Catatan: untuk memperluas tipe bawaan seperti list atau tuple, Anda harus mengimplementasikan __getslice__untuk versi python 2.X. lihat docs.python.org/2/reference/datamodel.html#object.__getslice__
gregorySalvan
@gregorySalvan: Bukankah contoh kompatibilitas di bawah bagian itu berulang?
Eric
3
@Eric: Tidak, karena kehadiran titik dua kedua melewati __get/set/delslice__. Ini cukup halus.
user2357112 mendukung Monica
@ user2357112: Wow, benar-benar melewatkan titik dua itu - terima kasih!
Eric
@alancalvitti IIRC, itu untuk membuat kelas gaya baru dengan Python 2.
wjandrea
64

Saya memiliki daftar "sintetis" (yang datanya lebih besar daripada yang ingin Anda buat dalam memori) dan __getitem__tampilan saya seperti ini:

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

Irisan tidak mengembalikan jenis yang sama, yang tidak-tidak, tetapi berhasil untuk saya.

Walter Nissen
sumber
1
Bukankah jika key> = len (self) menjadi jika key <0 atau key> = len (self)? Bagaimana jika kunci <-len (self) dilewatkan?
estan
20

Bagaimana cara mendefinisikan kelas getitem untuk menangani indeks biasa dan pemotongan?

Objek Slice dibuat secara otomatis saat Anda menggunakan titik dua dalam notasi subskrip - dan itulah yang diteruskan __getitem__. Gunakan isinstanceuntuk memeriksa apakah Anda memiliki objek 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)

Katakanlah kami menggunakan objek rentang, tetapi kami ingin irisan mengembalikan daftar daripada objek rentang baru (seperti yang dilakukannya):

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

Kami tidak dapat membuat rentang subkelas karena keterbatasan internal, tetapi kami dapat mendelegasikannya:

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)

Kami tidak memiliki objek Range yang dapat diganti dengan sempurna, tetapi ini cukup dekat:

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

Untuk lebih memahami notasi slice, berikut contoh penggunaan 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, perhatikan:

Di Python 2, ada metode usang yang mungkin perlu Anda ganti saat membuat subclass beberapa tipe bawaan.

Dari dokumentasi model data :

object.__getslice__(self, i, j)

Tidak digunakan lagi sejak versi 2.0: Mendukung objek slice sebagai parameter untuk __getitem__()metode ini. (Namun, tipe bawaan di CPython saat ini masih diimplementasikan __getslice__(). Oleh karena itu, Anda harus menggantinya di kelas turunan saat mengimplementasikan pemotongan.)

Ini hilang dengan Python 3.

Aaron Hall
sumber
7

Untuk memperluas jawaban Aaron, untuk hal-hal seperti numpy, Anda dapat melakukan pemotongan multi-dimensi dengan memeriksa apakah givenadalah 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]

``

Keluaran:

('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))
Eric Cousineau
sumber
Sebagai tindak lanjut kecil, berikut adalah contoh penggunaan ini untuk memetakan antara pengindeksan MATLAB dan pengindeksan NumPy (yang saat ini tidak didukung dalam MATLAB R2016b), dengan contoh penggunaannya .
Eric Cousineau