Estou tentando entender quando usar __getattr__
ou __getattribute__
. A documentação mencionada __getattribute__
se aplica a classes de novo estilo. O que são classes de novo estilo?
Estou tentando entender quando usar __getattr__
ou __getattribute__
. A documentação mencionada __getattribute__
se aplica a classes de novo estilo. O que são classes de novo estilo?
Respostas:
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__
.
__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?
__getattribute__
.
objec.__getattribute__
invoca myclass.__getattr__
nas circunstâncias certas.
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.mymin
e os obj1.mymax
atributos, tudo funciona bem. Mas quando eu tento acessar obj1.mycurrent
atributo - 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.mycurrent
atributo - 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 AttributeError
exceçã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]
Se sua classe contêm ambos GetAttr e getAttribute métodos mágicos, em seguida, __getattribute__
é chamado pela primeira vez. Mas se __getattribute__
gera
AttributeError
exceçã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)
__getattribute__
mas certamente isso não é. Porque, por seu exemplo, tudo o que você está fazendo __getattribute__
é gerar uma AttributeError
exceçã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.
current
é definido em instâncias Count
(ver __init__
), então simplesmente levantar AttributeError
se 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
...
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
__getattr__()
implementações do mundo real aceitam apenas um conjunto finito de nomes de atributos válidos aumentando AttributeError
para 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__()
.
defaultdict
.
__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.
As novas classes de estilo são herdadas object
ou 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.
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
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.