Diferencia entre __getattr__ vs __getattribute__

Respuestas:

497

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 una reserva para los atributos faltantes, y es probablemente uno de los dos que desea.

__getattribute__se invoca antes de mirar los atributos reales en el objeto, por lo que puede ser difícil de implementar correctamente. Puede 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 clase base explícita. Pero la distinción entre clases de estilo antiguo y de estilo nuevo no es importante al elegir entre __getattr__y __getattribute__.

Casi seguro que quieres __getattr__.

Ned Batchelder
fuente
66
¿Puedo implementar ambos en la misma clase? Si puedo, ¿qué es para implementar ambos?
Alcott
13
@Alcott: puede implementar ambos, pero no estoy seguro de por qué lo haría. __getattribute__será llamado para cada acceso, y __getattr__será llamado para los tiempos que __getattribute__plantean una AttributeError. ¿Por qué no simplemente mantenerlo todo en uno?
Ned Batchelder
10
@NedBatchelder, si desea anular (condicionalmente) las llamadas a los métodos existentes, debería utilizarlas __getattribute__.
Jace Browning
28
"Para evitar una recursión infinita en este 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, objeto .__ getattribute __ (self, name)".
kmonsoor
1
@kmonsoor. Esa es una buena razón para implementar ambos. objec.__getattribute__invoca myclass.__getattr__bajo las circunstancias correctas.
Físico loco
138

Veamos algunos ejemplos simples de ambos __getattr__y __getattribute__métodos mágicos.

__getattr__

Python llamará al __getattr__método cada vez que solicite un atributo que aún no se haya definido. En el siguiente ejemplo, mi clase Count no tiene ningún __getattr__método. Ahora en main cuando trato de acceder a ambos obj1.myminy los obj1.mymaxatributos todo funciona bien. Pero cuando intento acceder al obj1.mycurrentatributo, 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__método. Ahora, cuando intento acceder al obj1.mycurrentatributo, Python me devuelve lo que haya 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 el 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 un __getattribute__método en su clase, python invoca este método para cada atributo, independientemente de si existe o no. Entonces, ¿por qué necesitamos un __getattribute__método? Una buena razón es que puede evitar 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 comienza con la subcadena 'cur', Python genera una AttributeErrorexcepció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 una recursión infinita en el __getattribute__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 métodos mágicos getattr y getattribute , entonces __getattribute__se llama primero. Pero si __getattribute__genera AttributeErroruna excepción, se ignorará la excepción 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
fuente
1
No estoy seguro de cuál sería exactamente el caso de uso para anular, __getattribute__pero seguramente no lo sea. Porque según su ejemplo, todo lo que está haciendo __getattribute__es generar una AttributeErrorexcepción si el atributo no está presente en __dict__el objeto; pero realmente no necesita esto porque esta es la implementación predeterminada __getattribute__y, de hecho, __getattr__es exactamente lo que necesita como mecanismo de respaldo.
Rohit
@Rohit currentse define en instancias de Count(ver __init__), por lo que simplemente elevar AttributeErrorsi el atributo no está allí no es exactamente lo que está sucediendo: difiere __getattr__para todos los nombres que comienzan con 'cur', incluidos current, pero también curious, curly...
joelb
16

Este es solo un ejemplo basado en la explicación de Ned Batchelder .

__getattr__ ejemplo:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

Y si se usa el mismo ejemplo con __getattribute__Obtendría >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Simon K Bhatta4ya
fuente
99
En realidad, esto es terrible. Las __getattr__()implementaciones del mundo real solo aceptan un conjunto finito de nombres de atributos válidos al generar AttributeErrornombres de atributos no válidos, evitando así problemas sutiles y difíciles de depurar . Este ejemplo acepta incondicionalmente todos los nombres de atributos como válidos, un mal uso extraño (y francamente propenso a errores) __getattr__(). Si desea "control total" sobre la creación de atributos como en este ejemplo, lo desea __getattribute__().
Cecil Curry
3
@CecilCurry: todos los problemas que vinculó implican devolver implícitamente None en lugar de un valor, lo que esta respuesta no hace. ¿Qué hay de malo en aceptar todos los nombres de atributos? Es lo mismo que a defaultdict.
Nick Matteo
2
El problema es que __getattr__se llamará antes de la búsqueda de superclase. Esto está bien para una subclase directa de object, ya que los únicos métodos que realmente le interesan son los métodos mágicos que ignoran la instancia de todos modos, pero para cualquier estructura de herencia más compleja, elimina por completo la capacidad de heredar cualquier cosa del padre.
Físico loco
1
@Simon K Bhatta4ya, su última declaración impresa es un comentario. ¿Derecha? Es una línea larga y tediosa de leer (hay que desplazarse mucho en el lado derecho). ¿Qué hay de poner la línea después de la sección de código? O si desea ponerlo en la sección de código, creo que sería mejor dividirlo en dos líneas diferentes.
Md. Abu Nafee Ibna Zahid
6

Las clases de estilo nuevo son aquellas que subclasifican "objeto" (directa o indirectamente). Tienen un __new__método de clase además de __init__un comportamiento de bajo nivel algo más racional.

Por lo general, querrá anular __getattr__(si está anulando), de lo contrario tendrá dificultades para admitir la sintaxis "self.foo" dentro de sus métodos.

Información adicional: http://www.devx.com/opensource/Article/31482/0/page/4

Señor fooz
fuente
2

Al leer el PCB de Beazley & Jones, me topé con un caso de uso explícito y práctico __getattr__que ayuda a responder la parte del "cuándo" de la pregunta del OP. Del libro:

"El __getattr__()método es algo así como una búsqueda general de atributos. Es un método que se llama si el código intenta acceder a un atributo que no existe". Sabemos esto por las respuestas anteriores, pero en la receta de PCB 8.15, esta funcionalidad se utiliza para implementar el patrón de diseño de delegación . Si el Objeto A tiene un atributo Objeto B que implementa muchos métodos que el Objeto A desea delegar, en lugar de redefinir todos los métodos del Objeto B en el Objeto A solo para llamar a los métodos del Objeto B, defina un __getattr__()método de la siguiente manera:

def __getattr__(self, name):
    return getattr(self._b, name)

donde _b es el nombre del atributo del Objeto A que es un Objeto B. Cuando se llama a un método definido en el Objeto B en el Objeto A, el __getattr__método se invocará al final de la cadena de búsqueda. Esto también haría que el código sea más limpio, ya que no tiene una lista de métodos definidos solo para delegar a otro objeto.

soporific312
fuente