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?


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 .


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

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'

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.