bloco hasattr () vs try-except para lidar com atributos inexistentes

Respostas:

82

hasattrexecuta interna e rapidamente a mesma tarefa que o try/exceptbloco: é uma ferramenta muito específica, otimizada, de uma tarefa e, portanto, deve ser preferida, quando aplicável, à alternativa de uso muito geral.

Alex Martelli
fonte
8
Exceto que você ainda precisa do bloco try / catch para lidar com condições de corrida (se você estiver usando threads).
Douglas Leeder
1
Ou, o caso especial que acabei de encontrar: um django OneToOneField sem valor: hasattr (obj, field_name) retorna False, mas há um atributo com field_name: ele apenas levanta um erro DoesNotExist.
Matthew Schinckel
3
Note que hasattrirá capturar todas as exceções em Python 2.x. Veja minha resposta para um exemplo e uma solução alternativa trivial.
Martin Geisler
5
Um comentário interessante : trypode transmitir que a operação deve funcionar. Embora trya intenção de nem sempre seja essa, é comum, por isso pode ser considerada mais legível.
Ioannis Filippidis
88

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
ZeD
fonte
16
+1 para fornecer números interessantes e tangíveis. Na verdade, o "try" é eficiente quando contém o caso comum (ou seja, quando uma exceção Python é realmente excepcional).
Eric O Lebigot
Não tenho certeza de como interpretar esses resultados. O que é mais rápido aqui e em quanto?
Stevoisiak
2
@ StevenM.Vascellaro: Se o atributo existir, 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, tryquase 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.
Kevin
24

Existe uma terceira alternativa, muitas vezes melhor:

attr = getattr(obj, 'attribute', None)
if attr is not None:
     print attr

Vantagens:

  1. getattrnão tem o mau comportamento de engolir exceções apontado por Martin Geiser - nos Pythons antigos, hasattrengole até mesmo a KeyboardInterrupt.

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

  3. 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.)

  4. É mais curto que try/finallye geralmente menor que hasattr.

  5. Um except AttributeErrorbloqueio amplo pode pegar algo diferente do AttributeErrorsque você está esperando, o que pode levar a um comportamento confuso.

  6. 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.attributeestá definido como Nenhum, você precisará usar um valor de sentinela diferente.

poolie
fonte
1
+1 - Combina com dict.get ('my_key', 'default_value') e deve ser mais amplamente conhecido sobre
1
Ótimo para casos de uso comum em que você deseja verificar a existência e usar o atributo com o valor padrão.
dsalaj
18

Quase sempre uso hasattr: é a escolha correta para a maioria dos casos.

O caso problemático é quando uma classe sobrescreve __getattr__: hasattrirá capturar todas as exceções em vez de capturar exatamente AttributeErrorcomo você espera. Em outras palavras, o código abaixo será impresso b: False, embora seja mais apropriado ver uma ValueErrorexceçã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 hasattragora 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 getattrlidar com a situação e, em seguida, pode gerar a exceção apropriada.

Martin Geisler
fonte
2
Isso também foi melhorado um pouco no Python2.6 para que hasattrpelo menos não pegue KeyboardInterruptetc.
poolie
Ou, em vez de safehasattrapenas usar getattrpara copiar o valor em uma variável local se for usá-la, o que quase sempre é feito.
poolie
@poolie Isso é bom, eu não sabia que hasattrtinha sido melhorado assim.
Martin Geisler
Sim, isto é bom. Eu também não sabia disso até hoje, quando estava prestes a dizer a alguém para evitar hasattr, e fui verificar. Tivemos alguns erros bzr engraçados onde o hasattr engoliu ^ C.
poolie de
Estava enfrentando problemas durante a atualização do 2.7 para o 3.6. Esta resposta me ajuda a entender e resolver o problema.
Kamesh Jungi
13

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.

Roee Adler
fonte
1
1 por insistir em por que "tentar" tem um significado para as pessoas que lêem o código. :)
Eric O Lebigot
5

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

UncleZeiv
fonte
este é um grande problema que foi amplamente ignorado, em minha opinião.
Rick apoia Monica em
5

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

3 PERMISSÕES OU PERDÃO?

# 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
Jxramos
fonte
4

Se for apenas um atributo que você está testando, eu diria para usar hasattr. No entanto, se você estiver fazendo vários acessos a atributos que podem ou não existir, o uso de um trybloco pode economizar alguma digitação.

n8gray
fonte
3

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').

Douglas Leeder
fonte
2

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 .

cjs
fonte
+1 por insistir no fato de que "tentar" pode ser interpretado como "esta é uma situação excepcional", em relação ao teste condicional. :)
Eric O Lebigot
0

O primeiro.

Quanto mais curto, melhor. As exceções devem ser excepcionais.

Desconhecido
fonte
5
As exceções são muito comuns em Python - há uma no final de cada forinstrução e também hasattrusa 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.
Alex Martelli
@Alex só porque o analisador Python transforma essas instruções em 1 não significa que seja muito comum. Há uma razão pela qual eles fizeram esse açúcar sintático: para que você não fique preso ao problema de digitar o bloco try except.
Desconhecido
Se a exceção for excepcional, então "explícito é melhor", e a 2ª opção do pôster original é melhor, eu diria ...
Eric O Lebigot
0

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 hasattrusa apenas o primeiro caso para determinar o resultado. Alimento para o pensamento ;-)

Levita
fonte