Diferencia entre __getattr__ y __getattribute__

Resuelto Yarin asked hace 14 años • 8 respuestas

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?

Yarin avatar Jul 19 '10 09:07 Yarin
Aceptado

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__.

Ned Batchelder avatar Jul 19 '2010 02:07 Ned Batchelder

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.myminy obj1.mymaxa los atributos, todo funciona bien. Pero cuando intento acceder obj1.mycurrental 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.mycurrental 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 AttributeErroruna 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 AttributeErroruna 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)
N Randhawa avatar Aug 05 '2016 11:08 N Randhawa