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.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]
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)
__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.
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...
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 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__().
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 objecto 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.