Diferença entre __getattr__ vs __getattribute__

Respostas:

497

Uma diferença importante entre __getattr__e __getattribute__é que __getattr__só é invocada se o atributo não foi encontrado da maneira usual. É bom para implementar um fallback para atributos ausentes e provavelmente é o dois que você deseja.

__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.

Classes de estilo novo derivam object, classes de estilo antigo são aquelas no Python 2.x sem classe base explícita. Mas a distinção entre classes de estilo antigo e novo não é importante ao escolher entre __getattr__e __getattribute__.

Você quase certamente quer __getattr__.

Ned Batchelder
fonte
6
Posso implementar os dois na mesma classe? Se eu puder, o que é para implementar os dois?
Alcott
13
@ Alcott: você pode implementar os dois, mas não sei por que você faria. __getattribute__será chamado para todo acesso e __getattr__será chamado para os tempos que __getattribute__aumentaram um AttributeError. Por que não apenas manter tudo em um?
Ned Batchelder
10
@NedBatchelder, se você deseja (condicionalmente) substituir chamadas para métodos existentes, você deseja usar __getattribute__.
Jace Browning #
28
"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)."
kmonsoor 19/07/2014
1
@kmonsoor. Essa é uma boa razão para implementar ambos. objec.__getattribute__invoca myclass.__getattr__nas circunstâncias certas.
Mad Physicist
138

Vamos ver alguns exemplos simples de métodos __getattr__e __getattribute__métodos mágicos.

__getattr__

O Python chamará o __getattr__método sempre que você solicitar um atributo que ainda não tenha sido definido. No exemplo a seguir, minha classe Count não tem __getattr__método. Agora, principalmente, quando tento acessar os dois obj1.mymine os obj1.mymaxatributos, tudo funciona bem. Mas quando eu tento acessar obj1.mycurrentatributo - Python me dáAttributeError: 'Count' object has no attribute 'mycurrent'

class Count():
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

Agora minha classe Count tem __getattr__método. Agora, quando tento acessar o obj1.mycurrentatributo - python, retorna-me o que implementamos no meu __getattr__método. No meu exemplo, sempre que tento chamar um atributo que não existe, o python cria esse atributo e o define como valor inteiro 0.

class Count:
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax    

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)

__getattribute__

Agora vamos ver o __getattribute__método. Se você possui um __getattribute__método em sua classe, o python chama esse método para cada atributo, independentemente de existir ou não. Então, por que precisamos de __getattribute__método? Um bom motivo é que você pode impedir o acesso aos atributos e torná-los mais seguros, conforme mostrado no exemplo a seguir.

Sempre que alguém tenta acessar meus atributos que começam com a substring 'cur' python, gera AttributeErrorexceção. Caso contrário, ele retornará esse atributo.

class Count:

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

Importante: Para evitar recursões infinitas no __getattribute__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: object.__getattribute__(self, name)ou super().__getattribute__(item)e nãoself.__dict__[item]

IMPORTANTE

Se sua classe contêm ambos GetAttr e getAttribute métodos mágicos, em seguida, __getattribute__é chamado pela primeira vez. Mas se __getattribute__gera AttributeErrorexceção, a exceção será ignorada e o __getattr__método será invocado. Veja o seguinte exemplo:

class Count(object):

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattr__(self, item):
            self.__dict__[item]=0
            return 0

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
N Randhawa
fonte
1
Não tenho certeza do que exatamente seria o caso de uso para substituir, __getattribute__mas certamente isso não é. Porque, por seu exemplo, tudo o que você está fazendo __getattribute__é gerar uma AttributeErrorexceção se o atributo não estiver presente no __dict__objeto; mas você realmente não precisa disso porque essa é a implementação padrão __getattribute__e o fato __getattr__é exatamente o que você precisa como mecanismo de fallback.
Rohit #
@Rohit currenté definido em instâncias Count(ver __init__), então simplesmente levantar AttributeErrorse o atributo não está lá não é bem o que está acontecendo - que adia para __getattr__para todos os nomes que começam 'cur', incluindo current, mas também curious, curly...
joelb
16

Este é apenas um exemplo baseado na explicação de Ned Batchelder .

__getattr__ exemplo:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

E se o mesmo exemplo for usado com __getattribute__Você obterá >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Simon K Bhatta4ya
fonte
9
Na verdade, isso é terrível. As __getattr__()implementações do mundo real aceitam apenas um conjunto finito de nomes de atributos válidos aumentando AttributeErrorpara nomes de atributos inválidos, evitando problemas sutis e difíceis de depurar . Este exemplo aceita incondicionalmente todos os nomes de atributos como válidos - um uso bizarro (e francamente propenso a erros) __getattr__(). Se você deseja "controle total" sobre a criação de atributos, como neste exemplo, você deseja __getattribute__().
Cecil Curry
3
@CecilCurry: todos os problemas aos quais você vinculou envolvem o retorno implícito de Nenhum, em vez de um valor, o que esta resposta não faz. O que há de errado em aceitar todos os nomes de atributos? É o mesmo que um defaultdict.
Nick Matteo
2
O problema é que __getattr__será chamado antes da pesquisa da superclasse. Isso é bom para uma subclasse direta de object, já que os únicos métodos com os quais você realmente se importa são os métodos mágicos que ignoram a instância, mas para qualquer estrutura de herança mais complexa, você remove completamente a capacidade de herdar qualquer coisa do pai.
May Physicsist
1
@ Simon K Bhatta4ya, sua última declaração impressa é um comentário. Direita? É uma linha longa e tediosa de se ler (é preciso rolar muito no lado direito). Que tal colocar a linha após a seção de código? Ou, se você quiser colocá-lo na seção de código, acho que seria melhor dividi-lo em duas linhas diferentes.
Dr. Abu Nafee Ibna Zahid
14

As novas classes de estilo são herdadas objectou de outra nova classe de estilo:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

As classes de estilo antigo não:

class SomeObject:
    pass

Isso se aplica apenas ao Python 2 - no Python 3, todas as opções acima criarão classes de novo estilo.

Consulte 9. Classes (tutorial em Python), NewClassVsClassicClass e Qual é a diferença entre o estilo antigo e as novas classes de estilo no Python? para detalhes.

Sam Dolan
fonte
6

As classes de novo estilo são aquelas que subclassificam "objeto" (direta ou indiretamente). Eles têm um __new__método de classe além __init__e têm um comportamento de baixo nível um pouco mais racional.

Geralmente, você deseja substituir __getattr__(se estiver substituindo), caso contrário, será difícil dar suporte à sintaxe "self.foo" em seus métodos.

Informações adicionais: http://www.devx.com/opensource/Article/31482/0/page/4

Mr Fooz
fonte
2

Ao ler o Beazley & Jones PCB, deparei-me com um caso de uso explícito e prático, __getattr__que ajuda a responder à parte "quando" da pergunta do OP. Do livro:

"O __getattr__()método é como um exemplo geral para a pesquisa de atributos. É um método que é chamado se o código tentar acessar um atributo que não existe". Sabemos disso pelas respostas acima, mas na receita PCB 8.15, essa funcionalidade é usada para implementar o padrão de design da delegação . Se o Objeto A tiver um atributo Objeto B que implemente muitos métodos que o Objeto A deseja delegar, em vez de redefinir todos os métodos do Objeto B no Objeto A apenas para chamar os métodos do Objeto B, defina um __getattr__()método da seguinte maneira:

def __getattr__(self, name):
    return getattr(self._b, name)

onde _b é o nome do atributo do Objeto A que é um Objeto B. Quando um método definido no Objeto B é chamado no Objeto A, o __getattr__método será chamado no final da cadeia de pesquisa. Isso tornaria o código mais limpo também, já que você não possui uma lista de métodos definidos apenas para delegar a outro objeto.

soporific312
fonte