Zrozumienie metody __getitem__

143

Przejrzałem większość dokumentacji __getitem__w dokumentacji Pythona, ale nadal nie jestem w stanie pojąć jej znaczenia.

Wszystko, co mogę zrozumieć, to to, że __getitem__jest używany do wykonywania wywołań takich jak self[key]. Ale jaki z tego pożytek?

Powiedzmy, że mam klasę Pythona zdefiniowaną w ten sposób:

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"])

To zwraca wyniki zgodnie z oczekiwaniami. Ale po co używać __getitem__w pierwszej kolejności? Słyszałem również, że Python wywołuje __getitem__wewnętrznie. Ale dlaczego to robi?

Czy ktoś mógłby wyjaśnić to bardziej szczegółowo?

user1867151
źródło
To może być interesujące dla jeden przykład użycia: Jak prawidłowo podklasy dict i przesłanianie GetItem & setitem
Rogan Josh
4
__getitem__Wykorzystania w przykładzie nie ma wiele sensu, ale wyobrażam sobie, że trzeba napisać niestandardowy listy- słownika lub podobnego do klasy, która ma współpracować z istniejącym kodem, który używa []. Taka sytuacja __getitem__jest przydatna.
Pieter Witvoet

Odpowiedzi:

160

Cong Ma dobrze sobie radzi, wyjaśniając, do czego __getitem__służy - ale chcę podać przykład, który może być przydatny. Wyobraź sobie klasę, która modeluje budynek. W danych dla budynku zawiera szereg atrybutów, w tym opisy firm zajmujących poszczególne piętra:

Bez użycia __getitem__mielibyśmy taką klasę:

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) )

Moglibyśmy jednak użyć __getitem__(i jej odpowiednika __setitem__), aby uczynić użycie klasy Building „przyjemniejszym”.

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] )

To, czy użyjesz w __setitem__ten sposób, naprawdę zależy od tego, jak planujesz wyodrębnić swoje dane - w tym przypadku zdecydowaliśmy się traktować budynek jako kontener pięter (i możesz również zaimplementować iterator dla budynku, a może nawet możliwość cięcia - tzn. uzyskaj dane z więcej niż jednego piętra na raz - zależy to od Twoich potrzeb.

Kaczmarek 66
źródło
15
Po prostu podziel się czymś, czego nauczyłem się dopiero po wielokrotnym przeczytaniu odpowiedzi: gdy masz getitem , nie musisz jawnie wywoływać tej funkcji. Kiedy nazywa building1[2]to wywołanie, wewnętrznie wywołuje getitem. Tak więc celem @ tony-suffolk-66 jest to, że dowolną właściwość / zmienną klasy można pobrać w czasie wykonywania, po prostu wywołując nazwę obiektu [nazwa_zmiennej]. Po prostu wyjaśniam to, ponieważ początkowo nie było to dla mnie jasne i pisząc to tutaj, mając nadzieję, że to komuś pomoże. Usuń, jeśli niepotrzebne, proszę
mithunpaul
3
@mithunpaul notacja obiektu [indeks] nie jest używana do pobrania właściwości / zmiennej / atrybutu klasy - jest to indeksowanie w obiekcie kontenera - na przykład pobieranie obiektu podrzędnego od rodzica, gdzie rodzic utrzymuje listę swoich dzieci. W moim przykładzie - klasa Building jest kontenerem (w tym przypadku nazw pięter), ale może to być klasa kontenera dla klas Floor.
Tony Suffolk 66
Tyle że to nie będzie wspierać len(), a otrzymasz TypeError:TypeError: object of type 'Building' has no len()
Ciasto piekarz
Wspieranie len (i innych funkcji, takich jak iteracja itp.) Nie było celem mojego przykładu. Implementacja metody dunder_len jest jednak trywialna.
Tony Suffolk 66
@ TonySuffolk66: czy to prawda, że ​​____len____ określa iterowalne dla indeksu (piętra) w twoim przykładzie, na których pętle ____getitem____?
Alex
74

[]Składnia coraz przedmiot przez klucz lub indeks jest tylko cukier składni.

Podczas oceny a[i]wywołań Pythona a.__getitem__(i)(lub type(a).__getitem__(a, i), ale to rozróżnienie dotyczy modeli dziedziczenia i nie jest tutaj ważne). Nawet jeśli klasa of anie może jawnie definiować tej metody, zwykle jest ona dziedziczona z klasy nadrzędnej.

Wszystkie specjalne nazwy metod (Python 2.7) i ich semantyka są wymienione tutaj: https://docs.python.org/2.7/reference/datamodel.html#special-method-names

Cong Ma
źródło
8

Metoda magiczna __getitem__jest zasadniczo używana do uzyskiwania dostępu do elementów list, pozycji słownika, elementów tablic itp. Jest bardzo przydatna do szybkiego wyszukiwania atrybutów instancji.

Tutaj pokazuję to z przykładową klasą Osoba, której instancje można utworzyć za pomocą „imienia”, „wieku” i „daty urodzenia”. __getitem__Metoda jest napisany w taki sposób, że można uzyskać dostęp do indeksowanych atrybutów przykład, takie jak imię lub nazwisko, dnia, miesiąca lub roku DOB itp

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

Załóżmy, że jedno wejście użytkownika jest następujące:

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

Za pomocą __getitem__metody użytkownik może uzyskać dostęp do indeksowanych atrybutów. na przykład,

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
źródło
Świetny przykład! Cały czas szukałem sposobu zaimplementowania getitem, gdy init zawiera wiele parametrów i walczyłem ze znalezieniem odpowiedniej implementacji i wreszcie to zobaczyłem! Głosowano za i dziękuję!
Rahul P