Come posso sostituire __getattr__ in Python senza interrompere il comportamento predefinito?


186

Voglio sovrascrivere il __getattr__metodo su una classe per fare qualcosa di elegante, ma non voglio interrompere il comportamento predefinito.

Qual è il modo corretto per farlo?


20
"Rompere", chiede, non "cambiare". Questo è abbastanza chiaro: gli attributi "fantasiosi" non dovrebbero interferire con gli attributi incorporati e dovrebbero comportarsi il più possibile come loro. La risposta di Michael è sia corretta che utile.
Oolooney

Risposte:


268

L'override __getattr__dovrebbe andare bene - __getattr__viene chiamato solo come ultima risorsa, cioè se nell'istanza non ci sono attributi che corrispondono al nome. Ad esempio, se si accede foo.bar, __getattr__verrà chiamato solo se foonon ha alcun attributo chiamato bar. Se l'attributo è uno che non vuoi gestire, aumenta AttributeError:

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

Tuttavia, diversamente __getattr__, __getattribute__verrà chiamato per primo (funziona solo per le nuove classi di stile, cioè quelle che ereditano dall'oggetto). In questo caso, puoi conservare il comportamento predefinito in questo modo:

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

Consulta i documenti Python per ulteriori informazioni .


Bah, la tua modifica ha la stessa cosa che stavo mostrando nella mia risposta, +1.

12
Fantastico, a Python non sembra piacere chiamare i super __getattr__- qualche idea su cosa fare? ( AttributeError: 'super' object has no attribute '__getattr__')
gatoatigrado,

1
Senza vedere il tuo codice è difficile da dire, ma sembra che nessuna delle tue superclasse definisca getattr .
Colin vH,

Funziona con hasattr anche perché: "Questo è implementato chiamando getattr (oggetto, nome) e vedendo se genera un'eccezione o meno". docs.python.org/2/library/functions.html#hasattr
ShaBANG

1
-1 Questo non modificare il comportamento predefinito. Ora hai un AttributeErrorsenza il contesto dell'attributo negli argomenti di eccezione.
mercoledì

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

Grazie per questo. Volevo solo assicurarmi che il messaggio predefinito fosse corretto senza scavare nella fonte.
ShawnFumo,

4
Penso che self.__class__.__name__dovrebbe essere usato invece che self.__class__nel caso in cui la classe abbia la precedenza__repr__
Michael Scott Cuthbert, l'

3
È meglio della risposta accettata, ma sarebbe bello non dover riscrivere quel codice e perdere potenzialmente le modifiche a monte se la formulazione dovesse essere modificata o il contesto aggiuntivo fosse aggiunto all'oggetto eccezione in futuro.
mercoledì

14

Per estendere la risposta di Michael, se si desidera mantenere il comportamento predefinito utilizzando __getattr__, è possibile farlo in questo modo:

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

        # Default behaviour
        return self.__getattribute__(name)

Ora il messaggio di eccezione è più descrittivo:

>>> 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'

2
@ fed.pavlo sei sicuro? Forse hai mescolato __getattr__e __getattribute__?
José Luis,

colpa mia. Ho perso la chiamata a un metodo diverso dall'io. ; (
fed.pavlo,

2
Infatti la risposta di
@Michael
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.