2009-02-28 23 views
26

Leí en un anterior answer que el manejo de excepciones es barato en Python, por lo que no deberíamos hacer comprobaciones preconditional.¿Manejo de excepción barato en Python?

No he oído hablar de esto antes, pero soy relativamente nuevo en Python. El manejo de excepciones significa una llamada dinámica y un retorno estático, mientras que una declaración if es una llamada estática, retorno estático.

¿Cómo puede ser malo hacer la verificación y try-except ser bueno, parece ser al revés. ¿Alguien me puede explicar esto?

+2

Discusión relacionada: http://stackoverflow.com/questions/204308/checking-for-member-existence-in-python – tzot

+7

+1 Para cuestionar el evangelio en lugar de creer todo lo que lee :) –

Respuesta

24

Puede encontrar esta publicación útil: Try/Except Performance in Python: A Simple Test donde Patrick Altman hizo algunas pruebas simples para ver cuál es el rendimiento en varios escenarios de verificación precondicional (específico de las claves del diccionario en este caso) y utilizando solo excepciones. También se proporciona un código si desea adaptarlo para probar otros condicionales.

Las conclusiones a las que llegaron a:

partir de estos resultados, yo creo que es justo para determinar rápidamente una serie de conclusiones:

  1. Si hay una alta probabilidad de que el elemento no existe, entonces es mejor que lo compruebes con has_key.
  2. Si no van a hacer nada con la excepción si se levantó, entonces es mejor que no poner uno se la excepción
  3. Si es probable que el elemento existe, entonces hay una muy ligera ventaja de usar un bloque try/except en lugar de utilizar has_key, , sin embargo, la ventaja es muy leve.
+3

has_key no es tal buena idea. AFAIK, (somekey in dict)/dict.get es mejor. – batbrat

+3

Aconsejaría no poner la excepción que desea capturar en el excepto: porque entonces no notará que se están levantando otras excepciones y la depuración es más difícil. Pero las excepciones en mi humilde opinión deberían usarse raramente de todos modos. Si puedes usar una prueba. – MrTopf

+1

Estoy de acuerdo con los dos comentarios anteriores. Como punto de datos adicional, acabo de agregar casos usando la tecla 'if' en d' en lugar de 'if d.has_key ('clave')' para el script de referencia vinculado anteriormente, y me parece que es consistentemente el enfoque más rápido: se vuelve más rápido que las excepciones incluso en el caso donde la clave existe en el diccionario. Por supuesto, esta prueba no es muy científica, pero es interesante. Estaba usando 2.7.3 en x86_64. – Cartroo

4

Soy un principiante de python también. Aunque no puedo decir por qué el manejo de excepciones ha sido llamado barato en el contexto de esa respuesta, aquí están mis pensamientos:

Tenga en cuenta que la comprobación con if-elif-else tiene que evaluar una condición cada vez. El manejo de excepciones, incluida la búsqueda de un manejador de excepciones, se produce solo en condiciones excepcionales, lo que es probable que sea raro en la mayoría de los casos. Esa es una clara ganancia de eficiencia. Como lo señala Jay, es mejor usar lógica condicional en lugar de excepciones cuando hay una gran probabilidad de que la clave esté ausente. Esto se debe a que si la clave está ausente la mayor parte del tiempo, no es una condición excepcional.

Dicho esto, sugiero que no se preocupe por la eficiencia y más bien por el significado. Utilice el manejo de excepciones para detectar casos excepcionales y verificar condiciones cuando desee decidir sobre algo. Me recordó la importancia del significado de S.Lott ayer.

Caso en cuestión:

def xyz(key): 
    dictOb = {x:1, y:2, z:3} 
    #Condition evaluated every time 
    if dictOb.has_key(key): #Access 1 to dict 
     print dictOb[key] #Access 2 

Versus

#Exception mechanism is in play only when the key isn't found. 
def xyz(key): 
    dictOb = {x:1, y:2, z:3} 
    try: 
     print dictOb[key] #Access 1 
    except KeyError: 
     print "Not Found" 

En general, tener un código que se encarga de algo, como una clave que falta, si acaso necesita el manejo de excepciones, pero en situaciones como cuando la clave no está presente la mayor parte del tiempo, lo que realmente quiere hacer es decidir si la clave está presente => if-else. Python enfatiza y alienta a decir lo que quieres decir.

Por qué se prefieren Excepciones a si-elif ->

  1. Expresa el significado más claramente cuando se está buscando enemigo excepcional condiciones inusuales/aka inesperados en su código.
  2. Es más limpio y mucho más legible.
  3. Es más flexible.
  4. Se puede usar para escribir un código más conciso.
  5. Evita muchas verificaciones desagradables.
  6. Es más fácil de mantener.

Nota Cuando evitamos el uso de try-excepción, siguen siendo levantado excepciones. Las excepciones que no se manejan simplemente van al controlador predeterminado. Cuando usa try-except, puede manejar el error usted mismo. Puede ser más eficiente porque if-else requiere una evaluación de condición, mientras que buscar un controlador de excepción puede ser más económico. Incluso si esto es cierto, la ganancia de esto será demasiado menor como para no pensar en ello.

Espero que mi respuesta ayude.

+2

Encuentro que la versión if es mucho más legible, en realidad, especialmente si usa la sintaxis más reciente 'if key in dictOb'. Y aquí, KeyError es al menos bastante obvio; a veces es mucho más difícil decir qué significa la excepción sin tener que buscarla, mientras que el if suele ser explícito. – DNS

+0

Bueno, en este pequeño ejemplo no ayuda a la legibilidad. Solo intento ilustrar mi punto. Por otro lado, cuando tienes un número de condiciones excepcionales, se vuelve desordenado rápidamente, si usas if-else. Sé por experiencia personal que es una mala idea. – batbrat

+1

* Mala idea usar if-else. Aprender a leer excepciones no es muy difícil. Es más fácil que mantener el desordenado if-else. – batbrat

1

¿Qué son las llamadas y los retornos estáticos frente a los dinámicos, y por qué crees que las llamadas y los retornos son diferentes en Python dependiendo de si lo haces en un bloque try/except? Incluso si no está atrapando una excepción, Python aún tiene que manejar la llamada posiblemente generando algo, por lo que no importa a Python en cuanto a cómo se manejan las llamadas y las devoluciones.

Cada llamada de función en Python implica presionar los argumentos en la pila e invocar al invocable. Cada terminación de función es seguida por la persona que llama, en el cableado interno de Python, verificando si la terminación es exitosa o de excepción, y lo maneja en consecuencia. En otras palabras, si crees que hay un manejo adicional cuando te encuentras en un bloque try/except que de alguna manera se salta cuando no estás en uno, estás equivocado. Supongo que eso es de lo que se trataba la distinción entre "estática" y "dinámica".

Además, es una cuestión de estilo, y los desarrolladores experimentados de Python vienen a leer la excepción atrapando bien, de modo que cuando ven el intento apropiado/excepto alrededor de una llamada, es más legible que una verificación condicional.

8

Con Python, es fácil comprobar diferentes posibilidades para la velocidad - conoce la timeit module:

... ejemplo de sesión (utilizando la línea de comandos) que compara el costo de usar hasattr() vs .try/except para probar los atributos de objetos perdidos y presentes.

% timeit.py 'try:' ' str.__nonzero__' 'except AttributeError:' ' pass' 
100000 loops, best of 3: 15.7 usec per loop 
% timeit.py 'if hasattr(str, "__nonzero__"): pass' 
100000 loops, best of 3: 4.26 usec per loop 
% timeit.py 'try:' ' int.__nonzero__' 'except AttributeError:' ' pass' 
1000000 loops, best of 3: 1.43 usec per loop 
% timeit.py 'if hasattr(int, "__nonzero__"): pass' 
100000 loops, best of 3: 2.23 usec per loop 

Estos resultados muestran de temporización en el caso hasattr(), levantando una excepción es lento, pero la realización de una prueba es más lenta que no aumentar la excepción. Por lo tanto, en términos de tiempo de ejecución, usar una excepción para manejar casos excepcionales tiene sentido.

EDITAR: La opción de línea de comando -n tendrá un conteo lo suficientemente grande para que el tiempo de ejecución sea significativo. A quote from the manual:

Si -n no se da, un número adecuado de bucles se calcula tratando sucesivas potencias de 10 hasta que el tiempo total es al menos 0,2 segundos.

+0

Solo me pregunto, ¿hay alguna razón por la cual hubo diez veces más intentos para la tercera prueba? –

+0

Se agregó otra cita del manual: la tercera prueba fue demasiado rápida para 100000 bucles. – gimel

35

No te preocupes por las cosas pequeñas. Ya ha elegido uno de los lenguajes de scripting más lentos, por lo que tratar de optimizarlo hasta el código de operación no lo ayudará demasiado. La razón para elegir un lenguaje interpretado y dinámico como Python es optimizar su tiempo, no el de la CPU.

Si usa expresiones idiomáticas comunes, verá todos los beneficios de la creación rápida de prototipos y el diseño limpio y su código se ejecutará naturalmente más rápido a medida que se lanzan nuevas versiones de Python y se actualiza el hardware de la computadora.

Si tiene problemas de rendimiento, perfile su código y optimice sus algoritmos lentos. Pero, mientras tanto, use excepciones para situaciones excepcionales, ya que hará que cualquier refactorización que realice en última instancia sea mucho más fácil.

+7

No estoy de acuerdo. Python es lo suficientemente rápido. Es lo mismo con C y ensamblaje. Incrustas C en Python en cuellos de botella, al igual que incrusta el ensamblaje en C en cuellos de botella. – batbrat

+16

Por extraño que parezca, estoy de acuerdo con todo lo que dijo después de "No estoy de acuerdo". :) –

+2

Creo que no le gusta que Python se denomine "lenguaje de scripting [lento]". – Phob

9

"¿Alguien me puede explicar esto?"

Depende.

Aquí hay una explicación, pero no es útil. Tu pregunta surge de tus suposiciones. Dado que el mundo real entra en conflicto con sus suposiciones, debe significar que sus suposiciones son incorrectas. No hay mucha explicación, pero es por eso que estás preguntando.

"El manejo de excepciones significa una llamada dinámica y un retorno estático, mientras que una instrucción if es una llamada estática, retorno estático."

¿Qué significa "llamada dinámica"? ¿Buscar marcos de pila para un controlador? Supongo que de eso es de lo que estás hablando. Y una "llamada estática" de alguna manera está ubicando el bloque después de la instrucción if.

Quizás esta "llamada dinámica" no sea la parte más costosa de la operación. Tal vez la evaluación de la expresión if-statement sea un poco más cara que el simple "try-it-and-fail".

Resulta que las comprobaciones de integridad interna de Python son casi las mismas que su instrucción if, y deben hacerse de todos modos. Como Python siempre va a verificar, tu instrucción if es (en su mayoría) redundante.

Puede leer sobre el manejo de excepciones de bajo nivel en http://docs.python.org/c-api/intro.html#exceptions.


Editar

Más al punto: La si vs. excepto debate no importa.

Dado que las excepciones son económicas, no las etiquete como un problema de rendimiento.

Use lo que hace que su código claro y con significado. No pierdas tiempo en micro-optimizaciones como esta.

+3

Este debate no importa solo después de que te des cuenta de que no importa. Lo que quiero decir es que solo desde que sabemos que la diferencia de rendimiento es insignificante, podemos dejarlo sin problemas y enfocarnos en la calidad del código en sí. Si el manejo de excepciones fuera más lento que el 'si' normal en un orden de magnitud, entonces no podríamos decir que no importa. Fugas de abstracciones. No deberíamos preocuparnos por ellos la mayor parte del tiempo, pero no podemos ignorar el peligro potencial al ignorar ciegamente los problemas de rendimiento. – Michael

+0

+1 para la referencia – Wolf

22

Dejando de lado las mediciones de rendimiento que otros han dicho, el principio rector a menudo se estructura como "es más fácil pedir perdón que pedir permiso" frente a "mirar antes de saltar".

Considere estas dos fragmentos:

# Look before you leap 
if not os.path.exists(filename): 
    raise SomeError("Cannot open configuration file") 
f = open(filename) 

vs

# Ask forgiveness ... 
try: 
    f = open(filename) 
except IOError: 
    raise SomeError("Cannot open configuration file") 

Equivalente? Realmente no. Los sistemas operativos son sistemas de toma múltiple. ¿Qué sucede si el archivo se eliminó entre la prueba de llamada 'existente' y 'abierta'?

¿Qué ocurre si el archivo existe pero no es legible? ¿Qué ocurre si se trata de un nombre de directorio en lugar de un archivo? Puede haber muchos modos de falla posibles y verificarlos a todos es mucho trabajo. Especialmente dado que la llamada 'abierta' ya verifica e informa todas las posibles fallas.

La directriz debe ser para reducir la posibilidad de estado incoherente, y la mejor manera de hacerlo es usar excepciones en lugar de prueba/llamada.

1

El mensaje general, como dijo S.Lott, es que try/except no duele por lo que debe sentirse en libertad de usarlo cuando lo considere apropiado.

Este debate a menudo se denomina "LBYL vs EAFP", es decir "mira antes de saltar" frente a "más fácil pedir perdón que permiso". Alex Martelli pesa sobre el tema aquí: http://mail.python.org/pipermail/python-list/2003-May/205182.html Este debate tiene casi seis años, pero no creo que los problemas básicos hayan cambiado mucho.

Cuestiones relacionadas