Comprendre la méthode __getitem__

140

J'ai parcouru la plupart de la documentation de __getitem__la documentation Python, mais je suis toujours incapable d'en saisir la signification.

Donc, tout ce que je peux comprendre, c'est qu'il __getitem__est utilisé pour implémenter des appels comme self[key]. Mais à quoi cela sert-il?

Disons que j'ai une classe python définie de cette manière:

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 renvoie les résultats comme prévu. Mais pourquoi l'utiliser __getitem__en premier lieu? J'ai également entendu dire que Python appelle en __getitem__interne. Mais pourquoi le fait-il?

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

user1867151
la source
Cela peut être intéressant pour un exemple d'utilisation: Comment correctement sous- classer dict et remplacer getitem & setitem
roganjosh
4
L' __getitem__utilisation dans votre exemple n'a pas beaucoup de sens, mais imaginez que vous devez écrire une classe personnalisée de type liste ou dictionnaire, qui doit fonctionner avec le code existant qui utilise []. C'est une situation où __getitem__c'est utile.
Pieter Witvoet

Réponses:

158

Cong Ma fait un bon travail pour expliquer à quoi __getitem__sert - mais je veux vous donner un exemple qui pourrait être utile. Imaginez une classe qui modélise un bâtiment. Dans les données du bâtiment, il comprend un certain nombre d'attributs, y compris des descriptions 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 cependant utiliser __getitem__(et son équivalent __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 ça dépend vraiment de la façon dont vous envisagez d'abstraire vos données - dans ce cas, nous avons décidé de traiter un bâtiment comme un conteneur d'étages (et vous pouvez également implémenter un itérateur pour le bâtiment, et peut-être même la possibilité de découper - c'est-à-dire obtenir les données de plus d'un étage à la fois - cela dépend de ce dont vous avez besoin.

Tony Suffolk 66
la source
15
Juste pour partager quelque chose que j'ai appris seulement après avoir lu la réponse plusieurs fois: une fois que vous avez un getitem, vous n'avez pas à appeler explicitement cette fonction. Quand il appelle building1[2]cet appel lui-même appelle le getitem. Donc, le point que @ tony-suffolk-66 fait est que toute propriété / variable de la classe peut être récupérée pendant l'exécution en appelant simplement objectname [variablename]. Simplement clarifier cela car ce n'était pas clair pour moi au départ et l'écrire ici en espérant que cela aide quelqu'un. Supprimer si redondant s'il vous plaît
mithunpaul
3
@mithunpaul la notation objet [index] n'est pas utilisée pour obtenir une propriété / variable / attribut d'une classe - elle est indexée sur un objet conteneur - par exemple pour récupérer 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, les noms de sol), mais cela pourrait être une classe de conteneur pour les classes Floor.
Tony Suffolk 66
Sauf qu'il ne supportera pas len(), et vous obtiendrez un TypeError:TypeError: object of type 'Building' has no len()
Ciasto piekarz
Le support de len (et d'autres fonctionnalités telles que l'itération, etc.) n'était pas le but de mon exemple. L'implémentation d'une méthode dunder_len est cependant triviale.
Tony Suffolk 66
@ TonySuffolk66: est-ce exact que ____len____ détermine l'itérable pour l'index (étages) dans votre exemple sur quelles boucles ____getitem____?
Alex
73

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

Lorsque vous évaluez les 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 ane définit pas 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

Cong Ma
la source
8

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

Ici, je montre cela avec un exemple de classe Person qui peut être instancié par «nom», «âge» et «dob» (date de naissance). La __getitem__méthode est écrite de manière à pouvoir accéder aux attributs d'instance indexés, tels que le prénom ou le nom, le jour, le mois ou l'année du dob, 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 la __getitem__méthode, 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'
user3503692
la source
Excellent exemple! Je cherchais partout comment implémenter getitem quand il y a plusieurs paramètres dans init et j'avais du mal à trouver une implémentation appropriée et j'ai finalement vu cela! J'ai voté et merci!
Rahul P le