Comprender el método __getitem__

138

He revisado la mayor parte de la documentación de los documentos de __getitem__Python, pero aún no puedo entender el significado de la misma.

Entonces, todo lo que puedo entender es que __getitem__se usa para implementar llamadas como self[key]. ¿Pero de qué sirve?

Digamos que tengo una clase de python definida de esta manera:

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

Esto devuelve los resultados como se esperaba. Pero, ¿por qué usar __getitem__en primer lugar? También he escuchado que Python llama __getitem__internamente. ¿Pero por qué lo hace?

¿Alguien puede explicar esto con más detalle?

usuario1867151
fuente
Esto puede ser de interés para un uso de ejemplo: Cómo subclasificar correctamente dict y anular getitem & setitem
roganjosh
44
El __getitem__uso en su ejemplo no tiene mucho sentido, pero imagine que necesita escribir una clase personalizada de tipo lista o diccionario, que tiene que funcionar con el código existente que usa []. Esa es una situación donde __getitem__es útil.
Pieter Witvoet

Respuestas:

157

Cong Ma hace un buen trabajo al explicar para qué __getitem__se utiliza, pero quiero darle un ejemplo que podría ser útil. Imagine una clase que modela un edificio. Dentro de los datos del edificio, incluye una serie de atributos, incluidas las descripciones de las empresas que ocupan cada piso:

Sin usar __getitem__tendríamos una clase como esta:

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

Sin embargo, podríamos usar __getitem__(y su contraparte __setitem__) para hacer que el uso de la clase Building sea 'más agradable'.

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

El uso de __setitem__este tipo realmente depende de cómo planee abstraer sus datos; en este caso, hemos decidido tratar un edificio como un contenedor de pisos (y también podría implementar un iterador para el Edificio, y tal vez incluso la capacidad de cortarlo) - es decir, obtener más de un piso a la vez - depende de lo que necesite.

Tony Suffolk 66
fuente
15
Solo para compartir algo que aprendí solo después de leer la respuesta varias veces: una vez que tiene un elemento , no tiene que llamar explícitamente a esa función. Cuando él llama a building1[2]esa llamada internamente, llama al getitem. Entonces, el punto que @ tony-suffolk-66 está haciendo es que, cualquier propiedad / variable de la clase se puede recuperar durante el tiempo de ejecución simplemente llamando a objectname [variablename]. Solo aclaro esto, ya que no estaba claro para mí inicialmente y lo escribí aquí esperando que ayude a alguien. Eliminar si es redundante por favor
mithunpaul
3
@mithunpaul, la notación de objeto [índice] no se usa para obtener una propiedad / variable / atributo de una clase, está indexando en un objeto contenedor, por ejemplo, recuperando un objeto hijo de un padre donde el padre mantiene una lista de sus hijos. En mi ejemplo, la clase Building es un contenedor (en este caso de nombres Floor), pero podría ser una clase contenedor para las clases Floor.
Tony Suffolk 66
Excepto que no será compatible len(), y obtendrá un TypeError:TypeError: object of type 'Building' has no len()
Ciasto piekarz
El soporte de len (y otras características como la iteración, etc.) no fue el propósito de mi ejemplo. Sin embargo, implementar un método dunder_len es trivial.
Tony Suffolk 66
@ TonySuffolk66: ¿es correcto que ____len____ determine el iterable para el índice (pisos) en su ejemplo en qué ____getitem____ bucles?
Alex
73

La []sintaxis para obtener el elemento por clave o índice es solo el azúcar de sintaxis.

Cuando evalúa las a[i]llamadas de Python a.__getitem__(i)(o type(a).__getitem__(a, i), pero esta distinción se trata de modelos de herencia y no es importante aquí). Incluso si la clase de ano puede definir explícitamente este método, generalmente se hereda de una clase ancestral.

Todos los nombres de métodos especiales (Python 2.7) y su semántica se enumeran aquí: https://docs.python.org/2.7/reference/datamodel.html#special-method-names

Cong Ma
fuente
8

El método mágico __getitem__se usa básicamente para acceder a elementos de la lista, entradas de diccionario, elementos de matriz, etc. Es muy útil para una búsqueda rápida de atributos de instancia.

Aquí estoy mostrando esto con una clase de ejemplo Persona que se puede instanciar por 'nombre', 'edad' y 'dob' (fecha de nacimiento). El __getitem__método está escrito de forma tal que se pueda acceder a los atributos de instancia indexados, como nombre o apellido, día, mes o año del 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

Supongamos que una entrada del usuario es la siguiente:

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

Con la ayuda del __getitem__método, el usuario puede acceder a los atributos indexados. p.ej,

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
fuente
Gran ejemplo! ¡Estaba buscando por todas partes cómo implementar getitem cuando hay múltiples parámetros en init y estaba luchando por encontrar una implementación adecuada y finalmente lo vi! ¡Votado y gracias!
Rahul P