¿Cómo anulo __getattr__ en Python sin romper el comportamiento predeterminado?

186

Quiero anular el __getattr__método en una clase para hacer algo elegante, pero no quiero romper el comportamiento predeterminado.

¿Cuál es la forma correcta de hacer esto?

fundas
fuente
20
"Romper", pregunta, no "cambiar". Eso está bastante claro: los atributos "elegantes" no deberían interferir con los atributos integrados y deberían comportarse lo más posible como ellos. La respuesta de Michael es correcta y útil.
olooney

Respuestas:

268

La anulación __getattr__debería estar bien: __getattr__solo se llama como último recurso, es decir, si no hay atributos en la instancia que coincidan con el nombre. Por ejemplo, si accede foo.bar, __getattr__solo se llamará si foono se ha llamado ningún atributo bar. Si el atributo es uno que no desea manejar, aumente AttributeError:

class Foo(object):
    def __getattr__(self, name):
        if some_predicate(name):
            # ...
        else:
            # Default behaviour
            raise AttributeError

Sin embargo, a diferencia de __getattr__, __getattribute__se llamará primero (solo funciona para nuevas clases de estilo, es decir, aquellas que heredan del objeto). En este caso, puede conservar el comportamiento predeterminado de la siguiente manera:

class Foo(object):
    def __getattribute__(self, name):
        if some_predicate(name):
            # ...
        else:
            # Default behaviour
            return object.__getattribute__(self, name)

Vea los documentos de Python para más .

Michael Williamson
fuente
Bah, tu edición tiene lo mismo que estaba mostrando en mi respuesta, +1.
12
Genial, a Python no parece gustarle llamar a los súper, __getattr__¿alguna idea de qué hacer? ( AttributeError: 'super' object has no attribute '__getattr__')
gatoatigrado 11/06/2013
1
Sin ver su código, es difícil saberlo, pero parece que ninguna de sus superclases define getattr .
Colin vH
Esto también funciona con hasattr porque: "Esto se implementa llamando a getattr (objeto, nombre) y viendo si genera una excepción o no". docs.python.org/2/library/functions.html#hasattr
ShaBANG
1
-1 Esto no modificar el comportamiento por defecto. Ahora tiene un AttributeErrorsin el contexto del atributo en los argumentos de excepción.
wim
34
class A(object):
    def __init__(self):
        self.a = 42

    def __getattr__(self, attr):
        if attr in ["b", "c"]:
            return 42
        raise AttributeError("%r object has no attribute %r" %
                             (self.__class__.__name__, attr))

>>> a = A()
>>> a.a
42
>>> a.b
42
>>> a.missing
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __getattr__
AttributeError: 'A' object has no attribute 'missing'
>>> hasattr(a, "b")
True
>>> hasattr(a, "missing")
False
wim
fuente
Gracias por esto. Solo quería asegurarme de que tenía el mensaje predeterminado correcto sin excavar en la fuente.
ShawnFumo
44
Creo que self.__class__.__name__debería usarse en lugar de self.__class__en caso de que la clase se anule__repr__
Michael Scott Cuthbert
3
Esto es mejor que la respuesta aceptada, pero sería bueno no tener que volver a escribir ese código y potencialmente perder los cambios aguas arriba si se modifica la redacción o se agrega un contexto adicional al objeto de excepción en el futuro.
wim
14

Para ampliar la respuesta de Michael, si desea mantener el comportamiento predeterminado utilizando __getattr__, puede hacerlo así:

class Foo(object):
    def __getattr__(self, name):
        if name == 'something':
            return 42

        # Default behaviour
        return self.__getattribute__(name)

Ahora el mensaje de excepción es más descriptivo:

>>> foo.something
42
>>> foo.error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getattr__
AttributeError: 'Foo' object has no attribute 'error'
José Luis
fuente
2
@ fed.pavlo ¿estás seguro? Tal vez te mezcló __getattr__y __getattribute__?
José Luis
culpa mía. Perdí la llamada a un método diferente de uno mismo. ; (
fed.pavlo
2
De hecho, la respuesta de
@Michael