Perdón de Python vs. Permiso y mecanografía

44

En Python, a menudo escucho que es mejor "pedir perdón" (captura de excepción) en lugar de "pedir permiso" (verificación de tipo / condición). En lo que respecta a hacer cumplir la escritura de pato en Python, ¿es esto

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

mejor o peor que

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

en términos de rendimiento, legibilidad, "pitónico" o algún otro factor importante?

darkfeline
fuente
17
hay una tercera opción, no hacer nada y tratar a cualquier foo sin barra como un error
jk.
Recuerdo haber escuchado que hasattrse implementa con ese intento / captura exacto internamente. No estoy seguro de si es verdad ... (actuaría de manera diferente en las propiedades, ¿no? Tal vez estoy pensando en getattr...)
Izkata
@Izkata: La implementación dehasattr utiliza el equivalente C-API de getattr(devolver Truesi tiene éxito, Falsesi no), pero manejar excepciones en C es mucho más rápido.
Martijn Pieters
2
Acepté la respuesta de martijn, pero me gustaría agregar que si estás tratando de establecer un atributo, definitivamente deberías considerar usar try / catch porque puede ser una propiedad sin un setter, en cuyo caso hasattr será verdadero , pero aún generará AttributeError.
darkfeline

Respuestas:

60

Realmente depende de la frecuencia con la que piense que se lanzará la excepción.

Ambos enfoques son, en mi opinión, igualmente válidos, al menos en términos de legibilidad y pitonismo. Pero si el 90% de sus objetos no tienen el atributo bar, notará una diferencia de rendimiento distinta entre los dos enfoques:

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

Pero si el 90% de los objetos no tienen el atributo, las tablas se han convertido:

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

Entonces, desde el punto de vista del rendimiento, debe elegir el enfoque que mejor se adapte a sus circunstancias.

Al final, un uso estratégico del timeitmódulo puede ser lo más Pythonic que puede hacer.

Martijn Pieters
fuente
1
Hace algunas semanas hice esta pregunta: programmers.stackexchange.com/questions/161798/... Allí pregunté si en idiomas mal escritos tenía que trabajar más haciendo verificaciones de tipo, y fui bombardeado por personas que decían que no. Sé que veo que tienes.
Tulains Córdova
@ user1598390: cuando define una API que espera una combinación homogénea de tipos, debe realizar algunas pruebas. La mayoría de las veces, no lo haces. Me temo que es un área específica de la que no se pueden derivar reglas sobre paradigmas en su conjunto.
Martijn Pieters
Bueno, cualquier desarrollo serio del sistema implica definir una API. Así que supongo que los lenguajes estrictos de tipo son los mejores porque tienes que codificar menos ya que el compilador comprueba los tipos por ti en tiempo de compilación.
Tulains Córdova
1
@GarethRees: establece un patrón para la segunda mitad de la respuesta donde paso un argumento a la función bajo prueba.
Martijn Pieters
1
Tenga en cuenta que hasattr, de todos modos, en realidad hace el equivalente C-api de un try, excepto bajo el capó, ya que resulta que la única forma general de determinar si un objeto tiene un atributo en Python es intentar acceder a él.
user2357112
11

En Python, a menudo obtienes un mejor rendimiento haciendo las cosas al estilo Python. Con otros lenguajes, el uso de excepciones para el control de flujo generalmente se considera una idea terrible porque las excepciones suelen imponer una sobrecarga extraordinaria. Pero debido a que esta técnica está explícitamente respaldada en Python, el intérprete está optimizado para este tipo de código.

Como con todas las preguntas de rendimiento, la única forma de estar seguro es perfilar su código. Escriba ambas versiones y vea cuál funciona más rápido. Aunque en mi experiencia, la "forma de Python" suele ser la forma más rápida.

tylerl
fuente
3

El rendimiento, creo, es una preocupación secundaria. Si surge, un generador de perfiles lo ayudará a centrarse en los cuellos de botella reales, que pueden ser o no la forma en que trata los posibles argumentos ilegales.

La legibilidad y la simplicidad, por otro lado, son siempre una preocupación principal. Aquí no hay reglas estrictas, solo usa tu criterio.

Este es un problema universal, pero las convenciones específicas del entorno o del lenguaje son relevantes. Por ejemplo, en Python generalmente está bien simplemente usar el atributo que espera y dejar que un posible AttributeError llegue a la persona que llama.

orip
fuente
-1

En términos de corrección , creo que el manejo de excepciones es el camino a seguir (aunque a veces uso el enfoque hasattr ()). El problema básico con confiar en hasattr () es que convierte las violaciones de los contratos de código en fallas silenciosas (este es un gran problema en JavaScript, que no arroja propiedades no existentes).

Joe
fuente
3
Su respuesta no parece agregar mucho más allá de lo que ya han dicho otros. A diferencia de otros sitios, los programadores esperan respuestas que expliquen por qué detrás de la respuesta. Tocas un buen punto con el tema de la falla silenciosa, pero no le prestas justicia. Leer lo siguiente puede ayudar: programmers.stackexchange.com/help/how-to-answer
La última respuesta que hice fue criticada por ser demasiado amplia. Pensé en probar breve y sucinto.
Joe