Comprender la diferencia entre __getattr__ y __getattribute__

206

Estoy tratando de entender la diferencia __getattr__y __getattribute__, sin embargo, no lo estoy logrando.

La respuesta a la pregunta de desbordamiento de pila Diferencia entre __getattr__vs__getattribute__ dice:

__getattribute__se invoca antes de mirar los atributos reales en el objeto, por lo que puede ser complicado implementarlo correctamente. Puede terminar en infinitas recursiones muy fácilmente.

No tengo ni idea de lo que eso significa.

Luego continúa diciendo:

Casi seguro que quieres __getattr__.

¿Por qué?

Leí que si __getattribute__falla, __getattr__se llama. Entonces, ¿por qué hay dos métodos diferentes que hacen lo mismo? Si mi código implementa las nuevas clases de estilo, ¿qué debo usar?

Estoy buscando algunos ejemplos de código para aclarar esta pregunta. Busqué en Google lo mejor que pude, pero las respuestas que encontré no discuten el problema a fondo.

Si hay alguna documentación, estoy listo para leerla.

usuario225312
fuente
2
si ayuda, de los documentos "Para evitar una recursión infinita en este método, su implementación siempre debe llamar al método de clase base con el mismo nombre para acceder a los atributos que necesita, por ejemplo, objeto .__ getattribute __ (self, name). " [ docs.python.org/2/reference/…
kmonsoor

Respuestas:

307

Algunos conceptos básicos primero.

Con los objetos, debe lidiar con sus atributos. Normalmente lo hacemos instance.attribute. A veces necesitamos más control (cuando no sabemos el nombre del atributo de antemano).

Por ejemplo, instance.attributese convertiría getattr(instance, attribute_name). Con este modelo, podemos obtener el atributo al proporcionar el nombre_atributo como una cadena.

Uso de __getattr__

También puede decirle a una clase cómo lidiar con los atributos que no administra explícitamente y hacerlo a través del __getattr__método.

Python llamará a este método cada vez que solicite un atributo que aún no se haya definido, por lo que puede definir qué hacer con él.

Un caso de uso clásico:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Advertencias y uso de __getattribute__

Si necesita capturar cada atributo independientemente de si existe o no , úselo __getattribute__en su lugar. La diferencia es que __getattr__solo se solicitan atributos que en realidad no existen. Si configura un atributo directamente, haciendo referencia a ese atributo lo recuperará sin llamar __getattr__.

__getattribute__ Se llama todos los tiempos.

pyfunc
fuente
"Por ejemplo, instance.attribute se convertiría en getattr(instance, attribute_name) ". ¿No debería ser __getattribute__(instance, attribute_name)?
Md. Abu Nafee Ibna Zahid
1
@ Md.AbuNafeeIbnaZahid getattres una función incorporada. getattr(foo, 'bar')es equivalente a foo.bar.
wizzwizz4
Vale la pena señalar que __getattr__solo se llama si está definido, ya que no se hereda deobject
joelb
94

__getattribute__ se llama siempre que se produce un acceso de atributo.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Esto causará una recursión infinita. El culpable aquí es la línea return self.__dict__[attr]. Supongamos (está lo suficientemente cerca de la verdad) que todos los atributos están almacenados self.__dict__y disponibles por su nombre. La línea

f.a

intenta acceder a la a atributo de f. Esto requiere f.__getattribute__('a'). __getattribute__Luego intenta cargar self.__dict__. __dict__es un atributo de self == fPython y, por f.__getattribute__('__dict__')lo tanto, vuelve a intentar acceder al atributo '__dict__'. Esta es la recursión infinita.

Si se __getattr__hubiera utilizado en su lugar, entonces

  1. Nunca se habría ejecutado porque ftiene un aatributo.
  2. Si se hubiera ejecutado, (digamos que solicitó f.b ), entonces no habría sido llamado para buscar __dict__porque ya está allí y __getattr__se invoca solo si todos los demás métodos para encontrar el atributo han fallado .

La forma 'correcta' de escribir la clase anterior usando __getattribute__es

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)vincula el __getattribute__método de la superclase 'más cercana' (formalmente, la siguiente clase en el Orden de resolución de método de la clase, o MRO) al objeto actual selfy luego lo llama y deja que eso haga el trabajo.

Todos estos problemas se evitan mediante el uso de __getattr__ que permite a Python hacer lo normal hasta que no se encuentre un atributo. En ese punto, Python le da el control a su __getattr__método y le permite encontrar algo.

También vale la pena señalar que puede encontrarse con una recursión infinita con __getattr__ .

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Lo dejaré como ejercicio.

aaronasterling
fuente
60

Creo que las otras respuestas han hecho un gran trabajo al explicar la diferencia entre __getattr__y __getattribute__, pero una cosa que podría no estar clara es por qué querrías usarla __getattribute__. Lo bueno de esto __getattribute__es que esencialmente te permite sobrecargar el punto cuando accedes a una clase. Esto le permite personalizar cómo se accede a los atributos en un nivel bajo. Por ejemplo, supongamos que quiero definir una clase donde todos los métodos que solo toman un argumento propio se tratan como propiedades:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

Y del intérprete interactivo:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Por supuesto, este es un ejemplo tonto y probablemente nunca quieras hacer esto, pero te muestra el poder que puedes obtener al anular __getattribute__.

Jason Baker
fuente
6

He pasado por la excelente explicación de los demás. Sin embargo, encontré una respuesta simple de este blog Python Magic Methods y__getattr__ . Todos los siguientes son de allí.

Usando el __getattr__método mágico, podemos interceptar esa búsqueda de atributos inexistente y hacer algo para que no falle:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Pero si el atributo existe, __getattr__no se invocará:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__es similar a __getattr__, con la diferencia importante que __getattribute__interceptará CADA búsqueda de atributos, no importa si el atributo existe o no.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

En ese ejemplo, el dobjeto ya tiene un valor de atributo. Pero cuando intentamos acceder a él, no obtenemos el valor esperado original ("Python"); solo estamos recibiendo lo que sea __getattribute__devuelto. Significa que prácticamente hemos perdido el atributo de valor; se ha convertido en "inalcanzable".

Mathsyouth
fuente