__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 == f
Python 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
- Nunca se habría ejecutado porque
f
tiene un a
atributo.
- 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 self
y 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.