Perdão em Python vs. Permissão e Digitação de Pato


44

Em Python, ouço muitas vezes que é melhor "pedir perdão" (captura de exceção) em vez de "pedir permissão" (verificação de tipo / condição). No que diz respeito à imposição de digitação de pato em Python, isso é

try:
    x = foo.bar
except AttributeError:
    pass
else:
    do(x)

melhor ou pior que

if hasattr(foo, "bar"):
    do(foo.bar)
else:
    pass

em termos de desempenho, legibilidade, "pitônico" ou algum outro fator importante?


17
existe uma terceira opção, não faça nada e trate qualquer foo sem barra como um bug
jk.

Lembro-me de ouvir que hasattré implementado com essa tentativa / captura exata internamente. Não certo se é verdade ... (ele agiria de forma diferente sobre as propriedades, não seria Talvez eu estou pensando? getattr..)
Izkata

@ Izkata: A implementação dehasattr usa o equivalente C-API de getattr(retorne Truese for bem-sucedido, Falsese não), mas lidar com exceções em C é muito mais rápido.
Martijn Pieters

2
Aceitei a resposta de martijn, mas gostaria de acrescentar que, se você estiver tentando definir um atributo, definitivamente considere usar try / catch, pois pode ser uma propriedade sem um setter; nesse caso, hasattr será true , mas ainda gerará AttributeError.
darkfeline

Respostas:


60

Realmente depende de quantas vezes você acha que a exceção será lançada.

Ambas as abordagens são, na minha opinião, igualmente válidas, pelo menos em termos de legibilidade e pitonismo. Mas se 90% dos seus objetos não tiverem o atributo, barvocê notará uma diferença de desempenho distinta entre as duas abordagens:

>>> import timeit
>>> def askforgiveness(foo=object()):
...     try:
...         x = foo.bar
...     except AttributeError:
...         pass
... 
>>> def askpermission(foo=object()):
...     if hasattr(foo, 'bar'):
...         x = foo.bar
... 
>>> timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')
2.9459929466247559
>>> timeit.timeit('testfunc()', 'from __main__ import askpermission as testfunc')
1.0396890640258789

Mas se 90% dos seus objetos não têm o atributo, as mesas foram viradas:

>>> class Foo(object):
...     bar = None
... 
>>> foo = Foo()
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askforgiveness as testfunc, foo')
0.31336188316345215
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askpermission as testfunc, foo')
0.4864199161529541

Portanto, do ponto de vista do desempenho, você precisa escolher a abordagem que melhor funciona para suas circunstâncias.

No final, algum uso estratégico do timeitmódulo pode ser a coisa mais pitônica que você pode fazer.


1
Algumas semanas atrás, fiz a seguinte pergunta: programmers.stackexchange.com/questions/161798/… Lá, perguntei se, em linguagens pouco tipadas, você precisava trabalhar extra para verificar o tipo, e fui bombardeado por pessoas dizendo que não. Sei que vejo que você tem.
Tulains Córdova

@ user1598390: Quando você define uma API que espera uma mistura homogênea de tipos, é necessário fazer alguns testes. Na maioria das vezes, você não. Esta é uma área específica da qual você não pode derivar regras sobre paradigmas como um todo, receio.
Martijn Pieters

Bem, qualquer desenvolvimento sério do sistema envolve a definição de uma API. Então, acho que as linguagens estritas de tipo são melhores para isso, porque você precisa codificar menos, pois o compilador verifica os tipos para você em tempo de compilação.
Tulains Córdova

1
@GarethRees: estabelece um padrão para a segunda metade da resposta em que passo um argumento para a função em teste.
Martijn Pieters

1
Observe que hasattr, na verdade, ele faz o equivalente C-api de uma tentativa, exceto sob o capô de qualquer maneira, uma vez que a única maneira geral de determinar se um objeto tem um atributo no Python é tentar acessá-lo.
user2357112

11

Em python, você geralmente obtém melhor desempenho fazendo as coisas da maneira Python. Em outros idiomas, o uso de exceções para controle de fluxo é geralmente considerado uma péssima idéia, porque as exceções normalmente impõem uma sobrecarga extraordinária. Mas, como essa técnica é explicitamente recomendada em Python, o intérprete é otimizado para esse tipo de código.

Como em todas as questões de desempenho, a única maneira de ter certeza é criar um perfil do seu código. Escreva as duas versões e veja qual delas roda mais rápido. Embora, na minha experiência, o "caminho Python" seja tipicamente o caminho mais rápido.


3

Sinto-me que o desempenho é uma preocupação secundária. Se surgir, um criador de perfil ajudará você a se concentrar nos gargalos reais, que podem ou não ser como você trata possíveis argumentos ilegais.

Legibilidade e simplicidade, por outro lado, são sempre uma preocupação primordial. Não há regras rígidas aqui, apenas use seu julgamento.

Essa é uma questão universal, mas as convenções específicas ao ambiente ou ao idioma são relevantes. Por exemplo, no Python, geralmente é bom simplesmente usar o atributo que você espera e permitir que um possível AttributeError alcance o chamador.


-1

Em termos de correção , acho que o tratamento de exceções é o caminho a seguir (às vezes uso a abordagem hasattr ()). O problema básico de confiar em hasattr () é que ele transforma violações de contratos de código em falhas silenciosas (esse é um grande problema no JavaScript, que não gera propriedades não existentes).


3
Sua resposta não parece acrescentar muito além do que já foi declarado por outras pessoas. Ao contrário de outros sites, os programadores esperam respostas que expliquem o porquê da resposta. Você aborda um bom ponto com a questão da falha silenciosa, mas não fornece justiça a ela. A leitura do seguinte pode ajudar: programmers.stackexchange.com/help/how-to-answer

A última resposta que fiz foi criticada por ser muito ampla. Eu pensei que tentaria breve e sucinto.
Joe
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.