259 votes

Comprendre la méthode __getitem__

J'ai parcouru la plupart de la documentation de __getitem__ dans la documentation Python, mais je ne parviens toujours pas à en saisir la signification.

Donc tout ce que je peux comprendre c'est que __getitem__ est utilisé pour mettre en œuvre des appels comme self[key] . Mais à quoi cela sert-il ?

Disons que j'ai une classe python définie de cette façon :

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __getitem__(self,key):
        print ("Inside `__getitem__` method!")
        return getattr(self,key)

p = Person("Subhayan",32)
print (p["age"])

Cela donne les résultats escomptés. Mais pourquoi utiliser __getitem__ en premier lieu ? J'ai également entendu dire que Python appelle __getitem__ en interne. Mais pourquoi le fait-il ?

Quelqu'un peut-il expliquer cela plus en détail ?

0 votes

Cela peut être intéressant pour un exemple d'utilisation : Comment sous-classer correctement dict et surcharger getitem & setitem

9 votes

El __getitem__ dans votre exemple n'a pas beaucoup de sens, mais imaginez que vous ayez besoin d'écrire une classe personnalisée de type liste ou dictionnaire, qui doit fonctionner avec du code existant qui utilise [] . C'est une situation où __getitem__ est utile.

1 votes

Le principal cas d'utilisation, à mon avis, est lorsque vous écrivez une classe personnalisée qui représente une collection de choses. Cela vous permet d'utiliser l'indexation familière par liste/rayon comme planets[i] pour accéder à un élément donné, même si planets n'est pas réellement une liste (et il pourrait, sous couvert, utiliser n'importe quelle structure de données de son choix, comme une liste ou un graphe lié, ou implémenter n'importe quelle fonction non-listée de son choix, ce qu'une liste ne pourrait pas faire).

291voto

Tony Suffolk 66 Points 1030

Cong Ma fait un bon travail en expliquant ce que __getitem__ mais je veux vous donner un exemple qui pourrait vous être utile. Imaginez une classe qui modélise un bâtiment. Les données du bâtiment comprennent un certain nombre d'attributs, dont la description des entreprises qui occupent chaque étage :

Sans utiliser __getitem__ nous aurions une classe comme celle-ci :

class Building(object):
     def __init__(self, floors):
         self._floors = [None]*floors
     def occupy(self, floor_number, data):
          self._floors[floor_number] = data
     def get_floor_data(self, floor_number):
          return self._floors[floor_number]

building1 = Building(4) # Construct a building with 4 floors
building1.occupy(0, 'Reception')
building1.occupy(1, 'ABC Corp')
building1.occupy(2, 'DEF Inc')
print( building1.get_floor_data(2) )

Nous pourrions toutefois utiliser __getitem__ (et son homologue __setitem__ ) pour rendre l'utilisation de la classe Building plus "agréable".

class Building(object):
     def __init__(self, floors):
         self._floors = [None]*floors
     def __setitem__(self, floor_number, data):
          self._floors[floor_number] = data
     def __getitem__(self, floor_number):
          return self._floors[floor_number]

building1 = Building(4) # Construct a building with 4 floors
building1[0] = 'Reception'
building1[1] = 'ABC Corp'
building1[2] = 'DEF Inc'
print( building1[2] )

Que vous utilisiez __setitem__ comme cela dépend vraiment de la façon dont vous prévoyez d'abstraire vos données - dans ce cas, nous avons décidé de traiter un bâtiment comme un conteneur d'étages (et vous pourriez également mettre en œuvre un itérateur pour le bâtiment, et peut-être même la capacité de trancher - c'est-à-dire d'obtenir les données de plus d'un étage à la fois - cela dépend de ce dont vous avez besoin.

42 votes

Juste pour partager quelque chose que je n'ai appris qu'après avoir lu la réponse plusieurs fois : une fois que vous avez une getitem vous n'avez pas besoin d'appeler explicitement cette fonction. Lorsqu'il appelle building1[2] cet appel appelle lui-même en interne le getitem. Donc, ce que @tony-suffolk-66 veut dire, c'est que toute propriété/variable de la classe peut être récupérée pendant l'exécution en appelant simplement objectname[variablename]. Je clarifie simplement ce point car il n'était pas clair pour moi au départ et je l'écris ici en espérant que cela aidera quelqu'un. Supprimer si redondant s'il vous plaît

3 votes

@mithunpaul la notation object[index] n'est pas utilisée pour obtenir une propriété/variable/attribut d'une classe - il s'agit d'une indexation sur un objet conteneur - par exemple la récupération d'un objet enfant d'un parent où le parent maintient une liste de ses enfants. Dans mon exemple, la classe Building est un conteneur (dans ce cas, des noms d'étages), mais elle pourrait être une classe conteneur pour les classes d'étages.

0 votes

Sauf qu'il ne supportera pas len() et vous obtiendrez un TypeError : TypeError: object of type 'Building' has no len()

125voto

Cong Ma Points 278

El [] La syntaxe pour obtenir un élément par clé ou par index n'est que du sucre syntaxique.

Lorsque vous évaluez a[i] Appels Python a.__getitem__(i) (ou type(a).__getitem__(a, i) (mais cette distinction concerne les modèles d'héritage et n'est pas importante ici). Même si la classe de a peut ne pas définir explicitement cette méthode, elle est généralement héritée d'une classe ancêtre.

Tous les noms de méthodes spéciales (Python 2.7) et leur sémantique sont répertoriés ici : https://docs.python.org/2.7/reference/datamodel.html#special-method-names

9voto

user3503692 Points 48

La méthode magique __getitem__ est essentiellement utilisé pour accéder à des éléments de liste, des entrées de dictionnaire, des éléments de tableau, etc. Il est très utile pour une recherche rapide des attributs d'une instance.

Je le montre ici avec un exemple de classe Person qui peut être instanciée par 'name', 'age' et 'dob' (date de naissance). Le site __getitem__ est écrite de manière à ce que l'on puisse accéder aux attributs d'instance indexés, tels que le nom ou le prénom, le jour, le mois ou l'année du décès, etc.

import copy

# Constants that can be used to index date of birth's Date-Month-Year
D = 0; M = 1; Y = -1

class Person(object):
    def __init__(self, name, age, dob):
        self.name = name
        self.age = age
        self.dob = dob

    def __getitem__(self, indx):
        print ("Calling __getitem__")
        p = copy.copy(self)

        p.name = p.name.split(" ")[indx]
        p.dob = p.dob[indx] # or, p.dob = p.dob.__getitem__(indx)
        return p

Supposons qu'une entrée utilisateur soit la suivante :

p = Person(name = 'Jonab Gutu', age = 20, dob=(1, 1, 1999))

Avec l'aide de __getitem__ l'utilisateur peut accéder aux attributs indexés, par exemple,

print p[0].name # print first (or last) name
print p[Y].dob  # print (Date or Month or ) Year of the 'date of birth'

0 votes

Excellent exemple ! J'ai cherché partout comment mettre en place getitem lorsqu'il y a plusieurs paramètres dans init Je me suis battu pour trouver une implémentation correcte et j'ai finalement vu ceci ! Upvoted et merci !

8 votes

En utilisant getitem pour accéder à des attributs de ce type est horrible (à mon avis) - il vaut bien mieux écrire une propriété et créer un attribut virtuel en lecture seule. Pensez à la lisibilité. votre p[y].dob se lit comme si p était un conteneur - et non comme si p était une instance avec des attributs. Un attribut virtuel serait beaucoup plus agréable à lire pour le code utilisant votre module. Vous pouvez également - si vous insistez - utiliser _getattr pour implémenter un attribut virtuel mais une propriété est une solution plus propre.

0 votes

@TonySuffolk66 Pouvez-vous fournir un exemple de ce que vous voulez dire ? Peut-être réécrire la solution dans cette réponse en utilisant vos suggestions. Merci.

2voto

blue_note Points 701

Pour lisibilité y cohérence . Cette question fait partie de pourquoi la surcharge de l'opérateur existe, puisque __getitem__ est l'une des fonctions qui implémente cela.

Si vous obtenez une classe inconnue, écrite par un auteur inconnu, et que vous voulez ajouter son 3e élément à son 5e élément, vous pouvez très bien supposer que obj[3] + obj[5] fonctionnera.

À quoi ressemblerait cette ligne dans un langage qui ne prend pas en charge la surcharge des opérateurs ? Probablement quelque chose comme obj.get(3).add(obj.get(5)) ? ? Ou peut-être obj.index(3).plus(obj.index(5)) ? ?

Le problème de la seconde approche est que (1) elle est beaucoup moins lisible et (2) vous ne pouvez pas deviner, vous doivent consultez la documentation.

2voto

hochjager Points 1

À titre d'information, le __getitem__ vous permet également de transformer votre objet en un objet de type itérable .

Ejemplo: si elle est utilisée avec iter() il peut générer autant de int valeurs au carré comme vous le souhaitez :

class MyIterable:
    def __getitem__(self, index):
        return index ** 2

obj = MyIterable()
obj_iter = iter(obj)

for i in range(1000):
    print(next(obj_iter))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X