Estoy tratando de entender cuándo usar __getattr__
o __getattribute__
. La documentación mencionada se __getattribute__
aplica a las clases de estilo nuevo. ¿Qué son las clases de estilo nuevo?
Estoy tratando de entender cuándo usar __getattr__
o __getattribute__
. La documentación mencionada se __getattribute__
aplica a las clases de estilo nuevo. ¿Qué son las clases de estilo nuevo?
Respuestas:
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__
.
__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?
__getattribute__
.
objec.__getattribute__
invoca myclass.__getattr__
bajo las circunstancias correctas.
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.mymin
y los obj1.mymax
atributos todo funciona bien. Pero cuando intento acceder al obj1.mycurrent
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__
método. Ahora, cuando intento acceder al obj1.mycurrent
atributo, 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 AttributeError
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 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]
Si su clase contiene métodos mágicos getattr y getattribute , entonces __getattribute__
se llama primero. Pero si __getattribute__
genera
AttributeError
una 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)
__getattribute__
pero seguramente no lo sea. Porque según su ejemplo, todo lo que está haciendo __getattribute__
es generar una AttributeError
excepció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.
current
se define en instancias de Count
(ver __init__
), por lo que simplemente elevar AttributeError
si 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
...
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
__getattr__()
implementaciones del mundo real solo aceptan un conjunto finito de nombres de atributos válidos al generar AttributeError
nombres 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__()
.
defaultdict
.
__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.
Las clases de estilo nuevo heredan de object
o de otra clase de estilo nueva:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
Las clases de estilo antiguo no:
class SomeObject:
pass
Esto solo se aplica a Python 2: en Python 3, todo lo anterior creará nuevas clases de estilo.
Consulte 9. Clases (tutorial de Python), NewClassVsClassicClass y ¿Cuál es la diferencia entre el estilo antiguo y las nuevas clases de estilo en Python? para detalles.
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
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.