Usando try vs if en python

¿Hay una razón para decidir cuál de las try o if usar, cuando se prueba la variable para tener un valor?

Por ejemplo, hay una función que devuelve una lista o no devuelve un valor. Quiero comprobar el resultado antes de procesarlo. ¿Cuál de los siguientes sería más preferible y por qué?

 result = function(); if (result): for r in result: #process items 

o

 result = function(); try: for r in result: #process items except TypeError: pass; 

Discusión relacionada:

Comprobando la existencia de miembros en Python

A menudo escuchas que Python fomenta el estilo EAFP (“es más fácil pedir perdón que permiso”) sobre el estilo LBYL (“mira antes de saltar”). Para mí, es una cuestión de eficiencia y legibilidad.

En su ejemplo (diga que en lugar de devolver una lista o una cadena vacía, la función era devolver una lista o None ), si espera que el 99% del tiempo del result realmente contenga algo iterable, usaría el try/except enfoque. Será más rápido si las excepciones son realmente excepcionales. Si el result es None más del 50% del tiempo, entonces usar if es probablemente mejor.

Para apoyar esto con unas pocas medidas:

 >>> import timeit >>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking 0.06379691968322732 >>> timeit.timeit(setup="a=1;b=1", stmt="try:\na/b\nexcept ZeroDivisionError:\n pass") 0.0829463709378615 >>> timeit.timeit(setup="a=1;b=0", stmt="try:\na/b\nexcept ZeroDivisionError:\n pass") 0.5070195056614466 >>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\na/b") 0.11940114974277094 >>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\na/b") 0.051202772912802175 

Entonces, mientras que una sentencia if siempre te cuesta, es casi gratis configurar un bloque try/except . Pero cuando realmente ocurre una Exception , el costo es mucho mayor.

Moral:

  • Está perfectamente bien (y “pythonic”) usar try/except para control de flujo,
  • pero tiene más sentido cuando las Exception son realmente excepcionales.

De los documentos de Python:

EAFP

Más fácil pedir perdón que permiso. Este estilo de encoding de Python común asume la existencia de claves o atributos válidos y detecta excepciones si la suposición es falsa. Este estilo limpio y rápido se caracteriza por la presencia de muchas declaraciones try y except . La técnica contrasta con el estilo LBYL común a muchos otros idiomas, como C.

Su función no debe devolver tipos mixtos (es decir, lista o cadena vacía). Debe devolver una lista de valores o simplemente una lista vacía. Entonces no necesitarías probar nada, es decir, tu código se colapsa para:

 for r in function(): # process items 

Ignore mi solución si el código que proporciono no es obvio a primera vista y debe leer la explicación después del ejemplo de código.

¿Puedo asumir que el “valor no devuelto” significa que el valor devuelto es Ninguno? En caso afirmativo, o si el “no valor” es falso booleano, puede hacer lo siguiente, ya que su código esencialmente trata “no valor” como “no iterar”:

 for r in function() or (): # process items 

Si function() devuelve algo que no es Verdadero, itera sobre la tupla vacía, es decir, no ejecuta ninguna iteración. Esto es esencialmente LBYL.

Su segundo ejemplo está roto: el código nunca lanzará una excepción TypeError ya que puede iterar a través de cadenas y listas. La iteración a través de una cadena o lista vacía también es válida: ejecutará el cuerpo del bucle cero veces.

¿Cuál de los siguientes sería más preferible y por qué?

Mira antes de saltar es preferible en este caso. Con el enfoque de excepción, un TypeError podría ocurrir en cualquier parte de su cuerpo de bucle y podría ser atrapado y desechado, lo que no es lo que quiere y hará que la depuración sea difícil.

(Sin embargo, estoy de acuerdo con Brandon Corfman: la devolución de Ninguno para ‘no elementos’ en lugar de una lista vacía está rota. Es un hábito desagradable de los codificadores Java que no deberían verse en Python. O Java).

En general, la impresión que tengo es que las excepciones deben reservarse para circunstancias excepcionales. Si se espera que el result nunca esté vacío (pero podría estar, si, por ejemplo, un disco se colapsa, etc.), el segundo enfoque tiene sentido. Si, por otro lado, un result vacío es perfectamente razonable en condiciones normales, realizar una prueba con una sentencia if tiene más sentido.

Tenía en mente el escenario (más común):

 # keep access counts for different files file_counts={} ... # got a filename somehow if filename not in file_counts: file_counts[filename]=0 file_counts[filename]+=1 

en lugar del equivalente:

 ... try: file_counts[filename]+=1 except KeyError: file_counts[filename]=1 

Bobince señala sabiamente que envolver el segundo caso también puede capturar TypeErrors en el bucle, que no es lo que quieres. Sin embargo, si realmente desea utilizar un bash, puede probar si es iterable antes del bucle.

 result = function(); try: it = iter(result) except TypeError: pass else: for r in it: #process items 

Como puedes ver, es bastante feo. No lo sugiero, pero debería mencionarse para completar.

En lo que respecta al rendimiento, usar el bloque de prueba para el código que normalmente no genera excepciones es más rápido que usar la sentencia if cada vez. Por lo tanto, la decisión depende de la probabilidad de casos de excitación.

Como regla general, nunca debe usar try / catch o cualquier otra cosa que maneje excepciones para controlar el flujo. A pesar de que la iteración detrás de escena se controla mediante el aumento de StopIteration excepciones de StopIteration , aún debe preferir su primer fragmento de código al segundo.