Diferencia entre __getattr__ y __getattribute__
Estoy tratando de entender cuándo definir __getattr__
o __getattribute__
. La documentación de Python que menciona __getattribute__
se aplica a clases de nuevo estilo. ¿Qué son las clases de nuevo estilo?
Una diferencia clave entre __getattr__
y __getattribute__
es que __getattr__
solo se invoca si el atributo no se encontró de la manera habitual. Es bueno para implementar un respaldo para atributos faltantes y probablemente sea uno de los dos que desee.
__getattribute__
se invoca antes de observar los atributos reales del objeto, por lo que puede ser complicado implementarlo correctamente. Puedes terminar en infinitas recursiones muy fácilmente.
Las clases de estilo nuevo derivan de object
, las clases de estilo antiguo son aquellas en Python 2.x sin una clase base explícita. Pero la distinción entre clases de estilo antiguo y nuevo no es la importante al elegir entre __getattr__
y __getattribute__
.
Es casi seguro que lo deseas __getattr__
.
Veamos algunos ejemplos simples de ambos métodos __getattr__
y __getattribute__
mágicos.
__getattr__
Python llamará __getattr__
al método cada vez que solicite un atributo que aún no se haya definido. En el siguiente ejemplo, mi clase Count no tiene __getattr__
método. Ahora en principal, cuando intento acceder a ambos obj1.mymin
y obj1.mymax
a los atributos, todo funciona bien. Pero cuando intento acceder obj1.mycurrent
al atributo, Python me daAttributeError: 'Count' object has no attribute 'mycurrent'
class Count():
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent) --> AttributeError: 'Count' object has no attribute 'mycurrent'
Ahora mi clase Count tiene __getattr__
un método. Ahora, cuando intento acceder obj1.mycurrent
al atributo, Python me devuelve todo lo que he implementado en mi __getattr__
método. En mi ejemplo, cada vez que intento llamar a un atributo que no existe, Python crea ese atributo y lo establece en un valor entero 0.
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
def __getattr__(self, item):
self.__dict__[item]=0
return 0
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)
__getattribute__
Ahora veamos el __getattribute__
método. Si tiene __getattribute__
un método en su clase, Python invoca este método para cada atributo, independientemente de si existe o no. Entonces, ¿por qué necesitamos __getattribute__
un método? Una buena razón es que puede impedir el acceso a los atributos y hacerlos más seguros, como se muestra en el siguiente ejemplo.
Cada vez que alguien intenta acceder a mis atributos que comienzan con la subcadena 'cur', Python genera AttributeError
una excepción. De lo contrario devuelve ese atributo.
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
Importante: Para evitar la recursividad infinita en __getattribute__
el método, su implementación siempre debe llamar al método de la clase base con el mismo nombre para acceder a los atributos que necesita. Por ejemplo: object.__getattribute__(self, name)
o super().__getattribute__(item)
y noself.__dict__[item]
IMPORTANTE
Si su clase contiene los métodos mágicos getattr y getattribute__getattribute__
, se llama primero. Pero si __getattribute__
genera
AttributeError
una excepción, la excepción se ignorará y __getattr__
se invocará el método. Vea el siguiente ejemplo:
class Count(object):
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattr__(self, item):
self.__dict__[item]=0
return 0
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
# note this class subclass object
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)