Entendendo a diferença entre __getattr__ e __getattribute__

206

Estou tentando entender a diferença entre __getattr__e __getattribute__, no entanto, estou falhando nisso.

A resposta para a pergunta Stack Overflow Diferença entre __getattr__vs__getattribute__ diz:

__getattribute__é invocado antes de examinar os atributos reais no objeto e, portanto, pode ser difícil de implementar corretamente. Você pode terminar em recursões infinitas com muita facilidade.

Não tenho absolutamente nenhuma idéia do que isso significa.

Então continua dizendo:

Você quase certamente quer __getattr__.

Por quê?

Eu li que se __getattribute__falhar, __getattr__é chamado. Então, por que existem dois métodos diferentes fazendo a mesma coisa? Se meu código implementa as novas classes de estilo, o que devo usar?

Estou procurando alguns exemplos de código para esclarecer esta questão. Eu pesquisei no Google da melhor maneira possível, mas as respostas que encontrei não discutem o problema completamente.

Se houver alguma documentação, estou pronto para ler isso.

user225312
fonte
2
se isso ajudar, nos documentos "Para evitar recursões infinitas nesse método, sua implementação sempre deve chamar o método da classe base com o mesmo nome para acessar todos os atributos necessários, por exemplo, objeto .__ getattribute __ (self, name). " [ docs.python.org/2/reference/…
kmonsoor 19/07/2014

Respostas:

306

Alguns princípios básicos primeiro.

Com objetos, você precisa lidar com seus atributos. Normalmente fazemos instance.attribute. Às vezes, precisamos de mais controle (quando não sabemos o nome do atributo com antecedência).

Por exemplo, instance.attributese tornaria getattr(instance, attribute_name). Usando esse modelo, podemos obter o atributo fornecendo o attribute_name como uma string.

Uso de __getattr__

Você também pode dizer a uma classe como lidar com atributos que ela não gerencia explicitamente e fazer isso por meio do __getattr__método

O Python chamará esse método sempre que você solicitar um atributo que ainda não tenha sido definido, para que você possa definir o que fazer com ele.

Um caso de uso clássico:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Advertências e uso de __getattribute__

Se você precisar capturar todos os atributos, independentemente de existirem ou não , use-o __getattribute__. A diferença é que __getattr__apenas é chamado para atributos que realmente não existem. Se você definir um atributo diretamente, a referência a esse atributo o recuperará sem chamar __getattr__.

__getattribute__ é chamado o tempo todo.

pyfunc
fonte
"Por exemplo, instance.attribute se tornaria getattr(instance, attribute_name).". Não deveria ser __getattribute__(instance, attribute_name)?
Dr. Abu Nafee Ibna Zahid,
1
@ Md.AbuNafeeIbnaZahid getattré uma função interna . getattr(foo, 'bar')é equivalente a foo.bar.
Wizzwizz4 03/07/19
importante notar que __getattr__só é chamado se estiver definido, como não é herdada deobject
joelb
94

__getattribute__ é chamado sempre que ocorre um acesso ao atributo.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Isso causará uma recursão infinita. O culpado aqui é a linha return self.__dict__[attr]. Vamos fingir (está perto o suficiente da verdade) que todos os atributos estão armazenados self.__dict__e disponíveis pelo nome. A linha

f.a

tenta acessar o aatributo de f. Isso chama f.__getattribute__('a'). __getattribute__então tenta carregar self.__dict__. __dict__é um atributo de self == fchamadas python, f.__getattribute__('__dict__')que tenta novamente acessar o atributo '__dict__'. Isso é recursão infinita.

Se __getattr__tivesse sido usado em vez disso,

  1. Ele nunca seria executado porque fpossui um aatributo.
  2. Se tivesse sido executado (digamos que você solicitou f.b), não teria sido chamado para localizar __dict__porque já está lá e __getattr__será invocado apenas se todos os outros métodos de localização do atributo falharem .

A maneira 'correta' de escrever a classe acima usando __getattribute__é

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)vincula o __getattribute__método da superclasse 'mais próxima' (formalmente, a próxima classe no MRO da ordem de resolução de classe) ao objeto atual selfe, em seguida, o chama e deixa que faça o trabalho.

Todo esse problema é evitado usando o __getattr__que permite que o Python faça suas coisas normais até que um atributo não seja encontrado. Nesse ponto, o Python __getattr__passa o controle para o seu método e permite que ele crie alguma coisa.

Também é importante notar que você pode ter infinitas recursões com __getattr__.

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Vou deixar isso como um exercício.

aaronasterling
fonte
60

Eu acho que as outras respostas fizeram um ótimo trabalho em explicar a diferença entre __getattr__e __getattribute__, mas uma coisa que pode não estar clara é por que você gostaria de usar __getattribute__. O legal __getattribute__é que ele essencialmente permite sobrecarregar o ponto ao acessar uma classe. Isso permite que você personalize como os atributos são acessados ​​em um nível baixo. Por exemplo, suponha que eu queira definir uma classe em que todos os métodos que usam apenas um argumento próprio sejam tratados como propriedades:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

E do intérprete interativo:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Claro que este é um exemplo bobo e você provavelmente nunca iria querer fazer isso, mas mostra o poder que você pode obter com a substituição __getattribute__.

Jason Baker
fonte
6

Passei pela excelente explicação de outros. No entanto, eu encontrei uma resposta simples neste blog Python Magic Methods e__getattr__ . Todos os seguintes são de lá.

Usando o __getattr__método mágico, podemos interceptar essa pesquisa inexistente de atributo e fazer algo para que não falhe:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Mas se o atributo existir, __getattr__não será chamado:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__é semelhante a __getattr__, com a diferença importante que __getattribute__interceptará TODAS as pesquisas de atributo, não importa se o atributo existe ou não.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

Nesse exemplo, o dobjeto já possui um valor de atributo. Mas quando tentamos acessá-lo, não obtemos o valor esperado original ("Python"); estamos apenas recebendo o que quer que seja __getattribute__devolvido. Isso significa que praticamente perdemos o atributo value; tornou-se "inacessível".

mathsyouth
fonte