Implémentation du slicing dans __getitem__

112

J'essaie d'implémenter la fonctionnalité de tranche pour une classe que je crée qui crée une représentation vectorielle.

J'ai ce code jusqu'à présent, qui, je pense, implémentera correctement la tranche, mais chaque fois que je fais un appel comme v[4]où v est un vecteur, python renvoie une erreur concernant le manque de paramètres. J'essaie donc de comprendre comment définir la getitemméthode spéciale dans ma classe pour gérer à la fois les index simples et le découpage.

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]
nicotine
la source

Réponses:

118

La __getitem__()méthode recevra un sliceobjet lorsque l'objet est découpé. Il suffit de regarder les start, stopet les stepmembres de l' sliceobjet afin d'obtenir les composants de la tranche.

>>> 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
la source
10
Remarque: pour étendre les types intégrés tels que liste ou tuple, vous devez implémenter les __getslice__versions de python 2.X. voir docs.python.org/2/reference/datamodel.html#object.__getslice__
gregorySalvan
@gregorySalvan: Cet exemple de compatibilité sous cette section n'est-il pas récurrent?
Eric
3
@Eric: Non, car la présence du deuxième colon contourne __get/set/delslice__. C'est assez subtil, cependant.
user2357112 prend en charge Monica
@ user2357112: Wow, j'ai complètement raté ce deuxième deux-points - merci!
Eric
@alancalvitti IIRC, c'est pour créer des classes de nouveau style en Python 2.
wjandrea
64

J'ai une liste "synthétique" (une où les données sont plus grandes que vous voudriez créer en mémoire) et mon __getitem__ressemble à ceci:

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 tranche ne renvoie pas le même type, ce qui est un non-non, mais cela fonctionne pour moi.

Walter Nissen
la source
1
If key> = len (self) ne devrait-il pas être if key <0 ou key> = len (self)? Que faire si une clé <-len (self) est passée?
estan le
20

Comment définir la classe getitem pour gérer à la fois les index simples et le découpage?

Les objets Slice sont automatiquement créés lorsque vous utilisez un signe deux-points dans la notation en indice - et c'est ce à quoi est passé __getitem__. Utilisez isinstancepour vérifier si vous avez un objet tranche:

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)

Supposons que nous utilisions un objet range, mais que nous souhaitons que les tranches renvoient des listes au lieu de nouveaux objets range (comme c'est le cas):

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

Nous ne pouvons pas sous-classer range en raison de limitations internes, mais nous pouvons lui déléguer:

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)

Nous n'avons pas d'objet Range parfaitement remplaçable, mais c'est assez proche:

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

Pour mieux comprendre la notation de tranche, voici un exemple d'utilisation 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, soyez conscient:

Dans Python 2, il existe une méthode obsolète que vous devrez peut-être remplacer lors du sous-classement de certains types intégrés.

À partir de la documentation du modèle de données :

object.__getslice__(self, i, j)

Obsolète depuis la version 2.0: prend en charge les objets de tranche en tant que paramètres de la __getitem__()méthode. (Cependant, les types intégrés dans CPython sont toujours implémentés __getslice__(). Par conséquent, vous devez le remplacer dans les classes dérivées lors de l'implémentation du découpage.)

Cela a disparu dans Python 3.

Salle Aaron
la source
7

Pour étendre la réponse d'Aaron, pour des choses comme numpy, vous pouvez effectuer un découpage multidimensionnel en vérifiant si givenest 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]

''

Production:

('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))
Éric Cousineau
la source
À titre de suivi mineur, voici un exemple d'utilisation de ceci pour mapper entre l'indexation MATLAB et l'indexation NumPy (qui n'est actuellement pas prise en charge dans MATLAB R2016b), avec un exemple d'utilisation de celui-ci.
Eric Cousineau