Como substituir __getattr__ no Python sem interromper o comportamento padrão?

185

Quero substituir o __getattr__método em uma classe para fazer algo sofisticado, mas não quero interromper o comportamento padrão.

Qual é a maneira correta de fazer isso?

sheats
fonte
20
"Quebrar", pergunta, não "mudar". Isso é claro o suficiente: os atributos "sofisticados" não devem interferir nos atributos internos e devem se comportar o máximo possível como eles. A resposta de Michael é correta e útil.
olooney

Respostas:

268

A substituição __getattr__deve estar correta - __getattr__é chamada apenas como último recurso, ou seja, se não houver atributos na instância que correspondam ao nome. Por exemplo, se você acessar foo.bar, __getattr__somente será chamado se foonão tiver nenhum atributo chamado bar. Se o atributo for um que você não deseja manipular, aumente AttributeError:

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

No entanto, ao contrário __getattr__, __getattribute__será chamado primeiro (funciona apenas para novas classes de estilo, ou seja, aquelas que herdam do objeto). Nesse caso, você pode preservar o comportamento padrão da seguinte maneira:

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

Veja a documentação do Python para mais .

Michael Williamson
fonte
Bah, sua edição tem a mesma coisa que eu estava mostrando na minha resposta, +1.
12
Legal, o Python não parece gostar de chamar os super __getattr__- tem alguma idéia do que fazer? ( AttributeError: 'super' object has no attribute '__getattr__')
gatoatigrado
1
Sem ver seu código, é difícil dizer, mas parece que nenhuma das suas superclasses define getattr .
Colin vH
Isso funciona com o hasattr também porque: "Isso é implementado chamando getattr (objeto, nome) e ver se isso gera uma exceção ou não." docs.python.org/2/library/functions.html#hasattr
ShaBANG
1
-1 Este não modificar o comportamento padrão. Agora você tem um AttributeErrorsem o contexto do atributo nos argumentos da exceção.
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
fonte
Obrigado por isso. Só queria ter certeza de que a mensagem padrão estava correta sem procurar na fonte.
ShawnFumo #
4
Eu acho que self.__class__.__name__deve ser usado em vez de self.__class__no caso das substituições de classe__repr__
Michael Scott Cuthbert
3
É melhor que a resposta aceita, mas seria bom não ter que reescrever esse código e possivelmente perder as alterações upstream, caso a redação seja modificada ou um contexto extra seja adicionado ao objeto de exceção no futuro.
Wim
13

Para estender a resposta do Michael, se você deseja manter o comportamento padrão usando __getattr__, é possível:

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

        # Default behaviour
        return self.__getattribute__(name)

Agora a mensagem de exceção é mais descritiva:

>>> 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
fonte
2
@ fed.pavlo você tem certeza? Talvez você tenha misturado __getattr__e __getattribute__?
José Luis
foi mal. Eu perdi a chamada para um método diferente do eu. ; (
fed.pavlo 19/09/16
2
Na verdade @ resposta de Michael é incompleta sem esta resposta
Grijesh Chauhan