if hasattr(obj, 'attribute'):
# do somthing
vs
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
Qual deve ser preferido e por quê?
Respostas:
hasattr
executa interna e rapidamente a mesma tarefa que o try/except
bloco: é uma ferramenta muito específica, otimizada, de uma tarefa e, portanto, deve ser preferida, quando aplicável, à alternativa de uso muito geral.
hasattr
irá capturar todas as exceções em Python 2.x. Veja minha resposta para um exemplo e uma solução alternativa trivial.
try
pode transmitir que a operação deve funcionar. Embora try
a intenção de nem sempre seja essa, é comum, por isso pode ser considerada mais legível.
Quaisquer bancos que ilustram a diferença de desempenho?
hora em que é seu amigo
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "nonexistent")'
1000000 loops, best of 3: 1.87 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "a")'
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
c.a
except:
pass'
1000000 loops, best of 3: 0.247 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
c.nonexistent
except:
pass'
100000 loops, best of 3: 3.13 usec per loop
$
|positive|negative
hasattr| 0.446 | 1.87
try | 0.247 | 3.13
try
é quase duas vezes mais rápido que hasattr()
. Caso contrário, try
é cerca de 1,5x mais lento do que hasattr()
(e ambos são substancialmente mais lentos do que se o atributo existir). Provavelmente, isso ocorre porque, no caminho feliz, try
quase nada faz (o Python já está pagando pela sobrecarga de exceções, independentemente de você usá-las), mas hasattr()
requer uma pesquisa de nome e chamada de função. No caminho infeliz, ambos têm que fazer algum tratamento de exceção e um goto
, mas o hasattr()
fazem em C em vez de bytecode Python.
Existe uma terceira alternativa, muitas vezes melhor:
attr = getattr(obj, 'attribute', None)
if attr is not None:
print attr
Vantagens:
getattr
não tem o mau comportamento de engolir exceções apontado por Martin Geiser - nos Pythons antigos, hasattr
engole até mesmo a KeyboardInterrupt
.
A razão normal pela qual você está verificando se o objeto tem um atributo é para que você possa usar o atributo, e isso naturalmente leva a isso.
O atributo é lido atomicamente e está protegido de outras threads que alteram o objeto. (Porém, se esta for uma grande preocupação, você pode querer considerar o bloqueio do objeto antes de acessá-lo.)
É mais curto que try/finally
e geralmente menor que hasattr
.
Um except AttributeError
bloqueio amplo pode pegar algo diferente do AttributeErrors
que você está esperando, o que pode levar a um comportamento confuso.
Acessar um atributo é mais lento do que acessar uma variável local (especialmente se não for um atributo de instância simples). (Embora, para ser honesto, a microotimização em Python costuma ser uma missão tola.)
Uma coisa a ter cuidado é se você se preocupa com o caso em que obj.attribute
está definido como Nenhum, você precisará usar um valor de sentinela diferente.
Quase sempre uso hasattr
: é a escolha correta para a maioria dos casos.
O caso problemático é quando uma classe sobrescreve __getattr__
: hasattr
irá capturar todas as exceções em vez de capturar exatamente AttributeError
como você espera. Em outras palavras, o código abaixo será impresso b: False
, embora seja mais apropriado ver uma ValueError
exceção:
class X(object):
def __getattr__(self, attr):
if attr == 'a':
return 123
if attr == 'b':
raise ValueError('important error from your database')
raise AttributeError
x = X()
print 'a:', hasattr(x, 'a')
print 'b:', hasattr(x, 'b')
print 'c:', hasattr(x, 'c')
O erro importante, portanto, desapareceu. Isso foi corrigido no Python 3.2 (problema 9666 ), onde hasattr
agora apenas detecta AttributeError
.
Uma solução fácil é escrever uma função de utilitário como esta:
_notset = object()
def safehasattr(thing, attr):
return getattr(thing, attr, _notset) is not _notset
Vamos getattr
lidar com a situação e, em seguida, pode gerar a exceção apropriada.
hasattr
pelo menos não pegue KeyboardInterrupt
etc.
safehasattr
apenas usar getattr
para copiar o valor em uma variável local se for usá-la, o que quase sempre é feito.
hasattr
tinha sido melhorado assim.
hasattr
, e fui verificar. Tivemos alguns erros bzr engraçados onde o hasattr engoliu ^ C.
Eu diria que depende se sua função pode aceitar objetos sem o atributo por design , por exemplo, se você tiver dois chamadores para a função, um fornecendo um objeto com o atributo e o outro fornecendo um objeto sem ele.
Se o único caso em que você obterá um objeto sem o atributo for devido a algum erro, eu recomendo usar o mecanismo de exceções, mesmo que seja mais lento, porque acredito que é um design mais limpo.
Resumindo: acho que é um problema de design e legibilidade, e não de eficiência.
Se não ter o atributo não é uma condição de erro, a variante de tratamento de exceções tem um problema: ela capturaria também AttributeErrors que podem vir internamente ao acessar obj.attribute (por exemplo, porque o atributo é uma propriedade, então acessá-lo chama algum código).
Este assunto foi abordado na palestra EuroPython 2016 Escrevendo Python mais rápido por Sebastian Witowski. Aqui está uma reprodução de seu slide com o resumo do desempenho. Ele também usa a terminologia look antes de você pular nesta discussão, que vale a pena mencionar aqui para marcar essa palavra-chave.
Se o atributo estiver realmente ausente, implorar por perdão será mais lento do que pedir permissões. Portanto, como regra geral, você pode usar a forma de pedir permissão se souber que é muito provável que o atributo esteja ausente ou outros problemas que você possa prever. Caso contrário, se você espera que o código resulte em um código legível na maioria das vezes
# CASE 1 -- Attribute Exists
class Foo(object):
hello = 'world'
foo = Foo()
if hasatter(foo, 'hello'):
foo.hello
## 149ns ##
try:
foo.hello
except AttributeError:
pass
## 43.1 ns ##
## 3.5 times faster
# CASE 2 -- Attribute Absent
class Bar(object):
pass
bar = Bar()
if hasattr(bar, 'hello'):
bar.hello
## 428 ns ##
try:
bar.hello
except AttributeError :
pass
## 536 ns ##
## 25% slower
Eu sugeriria a opção 2. A opção 1 tem uma condição de corrida se algum outro thread estiver adicionando ou removendo o atributo.
Além disso, python tem um idioma , que EAFP ('mais fácil pedir perdão do que permissão') é melhor do que LBYL ('olhe antes de pular').
De um ponto de vista prático, na maioria das linguagens, usar uma condicional sempre será consideravelmente mais rápido do que lidar com uma exceção.
Se você deseja lidar com o caso de um atributo não existir em algum lugar fora da função atual, a exceção é o melhor caminho a seguir. Um indicador de que você pode querer usar uma exceção em vez de uma condicional é que a condicional meramente define um sinalizador e aborta a operação atual, e algo em outro lugar verifica esse sinalizador e executa uma ação com base nele.
Dito isso, como Rax Olgud aponta, a comunicação com os outros é um atributo importante do código, e o que você quer dizer ao dizer "esta é uma situação excepcional" em vez de "isso é algo que espero que aconteça" pode ser mais importante .
O primeiro.
Quanto mais curto, melhor. As exceções devem ser excepcionais.
for
instrução e também hasattr
usa uma. No entanto, "quanto mais curto é melhor" (e "mais simples é melhor"!) SE APLICA, então o hasattr mais simples, mais curto e mais específico é realmente preferível.
Pelo menos quando se trata apenas do que está acontecendo no programa, deixando de fora a parte humana da legibilidade, etc. (que é na verdade na maioria das vezes mais importante do que o desempenho (pelo menos neste caso - com aquele intervalo de desempenho), como Roee Adler e outros apontaram).
No entanto, olhando dessa perspectiva, torna-se uma questão de escolher entre
try: getattr(obj, attr)
except: ...
e
try: obj.attr
except: ...
já que hasattr
usa apenas o primeiro caso para determinar o resultado. Alimento para o pensamento ;-)