python - sucesion - ¿Cómo implemento__getattribute__ sin un error de recursión infinito?
sintaxis de recursividad (6)
¿Cómo se
__getattribute__
método__getattribute__
?
Se llama antes de la búsqueda punteada normal. Si aumenta AttributeError
, entonces llamamos __getattr__
.
El uso de este método es bastante raro. Solo hay dos definiciones en la biblioteca estándar:
$ grep -Erl "def __getattribute__/(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py
Mejores prácticas
La forma correcta de controlar mediante programación el acceso a un solo atributo es con property
. La clase D
debe escribir de la siguiente manera (con el colocador y el eliminador opcionalmente para reproducir el comportamiento aparente previsto):
class D(object):
def __init__(self):
self.test2=21
@property
def test(self):
return 0.
@test.setter
def test(self, value):
''''''dummy function to avoid AttributeError on setting property''''''
@test.deleter
def test(self):
''''''dummy function to avoid AttributeError on deleting property''''''
Y el uso:
>>> o = D()
>>> o.test
0.0
>>> o.test = ''foo''
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
Una propiedad es un descriptor de datos, por lo tanto, es lo primero que se busca en el algoritmo de búsqueda de puntos normal.
Opciones para __getattribute__
Tiene varias opciones si necesita implementar la búsqueda de cada atributo mediante __getattribute__
.
- elevar
AttributeError
, provocando que se__getattr__
a__getattr__
(si se implementó) - devolver algo de ella por
- usando
super
para llamar a la implementación principal (probablemente elobject
) - llamando
__getattr__
- implementando su propio algoritmo de búsqueda punteada de alguna manera
- usando
Por ejemplo:
class NoisyAttributes(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self, name):
print(''getting: '' + name)
try:
return super(NoisyAttributes, self).__getattribute__(name)
except AttributeError:
print(''oh no, AttributeError caught and reraising'')
raise
def __getattr__(self, name):
"""Called if __getattribute__ raises AttributeError"""
return ''close but no '' + name
>>> n = NoisyAttributes()
>>> nfoo = n.foo
getting: foo
oh no, AttributeError caught and reraising
>>> nfoo
''close but no foo''
>>> n.test
getting: test
20
Lo que originalmente querías
Y este ejemplo muestra cómo podrías hacer lo que originalmente querías:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name==''test'':
return 0.
else:
return super(D, self).__getattribute__(name)
Y se comportará así:
>>> o = D()
>>> o.test = ''foo''
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
>>> del o.test
Traceback (most recent call last):
File "<pyshell#216>", line 1, in <module>
del o.test
AttributeError: test
Revisión de código
Tu código con comentarios Tiene una búsqueda punteada en self en __getattribute__
. Es por eso que obtienes un error de recursión. Puede verificar si el nombre es "__dict__"
y usar super
para solucionarlo, pero eso no cubre __slots__
. Lo dejo como un ejercicio para el lector.
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name==''test'':
return 0.
else: # v--- Dotted lookup on self in __getattribute__
return self.__dict__[name]
>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
Quiero anular el acceso a una variable en una clase, pero devolver todos los demás normalmente. ¿Cómo puedo lograr esto con __getattribute__
?
Intenté lo siguiente (que también debería ilustrar lo que intento hacer) pero aparece un error de recursión:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name==''test'':
return 0.
else:
return self.__dict__[name]
>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
¿Seguro que quieres usar __getattribute__
? ¿Qué estás realmente tratando de lograr?
La forma más fácil de hacer lo que preguntas es:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
test = 0
o:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
@property
def test(self):
return 0
Editar: Tenga en cuenta que una instancia de D
tendría diferentes valores de test
en cada caso. En el primer caso, d.test
sería 20, en el segundo sería 0. d.test
que te explique por qué.
Edit2: Greg señaló que el ejemplo 2 fallará porque la propiedad es de solo lectura y el método __init__
intentó establecerlo en 20. Un ejemplo más completo sería:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
_test = 0
def get_test(self):
return self._test
def set_test(self, value):
self._test = value
test = property(get_test, set_test)
Obviamente, como clase esto es casi completamente inútil, pero te da una idea para moverte.
Aquí hay una versión más confiable:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
def __getattribute__(self, name):
if name == ''test'':
return 0.
else:
return super(D, self).__getattribute__(name)
Llama al método __ getattribute__ de la clase padre, eventualmente volviendo al objeto. __ getattribute__ método si otros antepasados no lo anulan.
En realidad, creo que quieres usar el método especial __getattr__
su lugar.
Cita de los documentos de Python:
__getattr__( self, name)
Se invoca cuando una búsqueda de atributo no ha encontrado el atributo en los lugares habituales (es decir, no es un atributo de instancia ni se encuentra en el árbol de clases por sí mismo). nombre es el nombre del atributo. Este método debe devolver el valor del atributo (calculado) o generar una excepción AttributeError.
Tenga en cuenta que si el atributo se encuentra a través del mecanismo normal,__getattr__()
no se llama. (Esta es una asimetría intencional entre__getattr__()
y__setattr__()
.) Esto se hace tanto por razones de eficiencia como porque de lo contrario__setattr__()
no tendría forma de acceder a otros atributos de la instancia. Tenga en cuenta que, al menos para las variables de instancia, puede simular el control total al no insertar ningún valor en el diccionario de atributos de la instancia (sino insertarlos en otro objeto). Vea el__getattribute__()
continuación para obtener un control total en las clases de nuevo estilo.
Nota: para que esto funcione, la instancia no debe tener un atributo de test
, por lo que la línea self.test=20
debe eliminarse.
Obtiene un error de recursividad porque su intento de acceder al atributo self.__dict__
dentro de __getattribute__
invoca nuevamente su __getattribute__
. Si utiliza __getattribute__
object
en su lugar, funciona:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name==''test'':
return 0.
else:
return object.__getattribute__(self, name)
Esto funciona porque object
(en este ejemplo) es la clase base. Al llamar a la versión base de __getattribute__
evitas el infierno recursivo en el que estabas antes.
Salida Ipython con código en foo.py:
In [1]: from foo import *
In [2]: d = D()
In [3]: d.test
Out[3]: 0.0
In [4]: d.test2
Out[4]: 21
Actualizar:
Hay algo en la sección titulada Acceso a más atributos para las clases de nuevo estilo en la documentación actual, donde recomiendan hacer exactamente esto para evitar la recursión infinita.
Referencia de lenguaje Python:
Para evitar recursiones infinitas 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, el
object.__getattribute__(self, name)
.
Sentido:
def __getattribute__(self,name):
...
return self.__dict__[name]
__dict__
por un atributo llamado __dict__
. Debido a que es un atributo, se llama a __dict__
en la búsqueda de __dict__
que llama __getattribute__
que llama ... yada yada yada
return object.__getattribute__(self, name)
Usar las clases base __getattribute__
ayuda a encontrar el atributo real.