Usar try vs if en python

145

¿Hay una razón para decidir cuál de tryoif construcciones a su uso, cuando se prueba variable tenga un valor?

Por ejemplo, hay una función que devuelve una lista o no devuelve un valor. Quiero verificar 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

artdanil
fuente
Tenga en cuenta que si su sección de elementos de #process es probable que arroje un TypeError, debe finalizar otro intento: excepto: block. Para este ejemplo específico, simplemente
usaría

Respuestas:

237

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

En su ejemplo (digamos 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 resultrealmente contenga algo iterable, usaría el try/exceptenfoque. Será más rápido si las excepciones son realmente excepcionales. Si resultes Nonemás del 50% del tiempo, entonces usandoif probablemente sea mejor .

Para apoyar esto con algunas 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:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Entonces, mientras que una ifdeclaración siempre le cuesta, es casi gratis configurar un try/exceptbloque. Pero cuando Exceptionocurre realmente, el costo es mucho mayor.

Moral:

  • Está perfectamente bien (y "pitónico") usar try/exceptpara el control de flujo,
  • pero tiene más sentido cuando los Exceptions son realmente excepcionales.

De los documentos de Python:

EAFP

Es más fácil pedir perdón que permiso. Este estilo común de codificación de Python supone la existencia de claves o atributos válidos y captura excepciones si el supuesto resulta falso. Este estilo limpio y rápido se caracteriza por la presencia de muchos tryy exceptdeclaraciones. La técnica contrasta con el estilo LBYL común a muchos otros lenguajes como C.

Tim Pietzcker
fuente
1
Gracias. Veo que en este caso la razón puede ser la expectativa del resultado.
artdanil 02 de
66
.... y esa es una (de muchas) razones por las que es tan difícil hacer un JIT de optimización real para Python. Como lo demuestra recientemente en beta LuaJIT 2, los lenguajes dinámicos pueden ser muy, muy rápidos; pero depende en gran medida del diseño del lenguaje inicial y el estilo que fomenta. (en una nota relacionada, el diseño del lenguaje es la razón por la cual incluso los mejores JIT de JavaScirpt no pueden compararse con LuaJIT 1, mucho menos 2)
Javier
1
@ 2rs2ts: acabo de hacer tiempos similares yo mismo. En Python 3, try/exceptfue un 25% más rápido que if key in d:en los casos en que la clave estaba en el diccionario. Era mucho más lento cuando la clave no estaba en el diccionario, como se esperaba, y era consistente con esta respuesta.
Tim Pietzcker
55
Sin embargo, sé que esta es una respuesta antigua: el uso de declaraciones como 1/1con timeit no es una gran opción, ya que se optimizarán (vea dis.dis('1/1')y tenga en cuenta que no ocurre división).
Andrea Corbellini
1
También depende de la operación que esté dispuesto a realizar. Una sola división es rápida, detectar un error es algo costoso. Pero incluso en los casos en que el costo del manejo de excepciones es aceptable, piense detenidamente en lo que le está pidiendo a la computadora que haga. Si la operación en sí es muy costosa, independientemente de sus resultados, y hay una forma económica de saber si el éxito es posible: utilícelo. Ejemplo: no copie la mitad de un archivo solo para darse cuenta de que no hay suficiente espacio.
Bachsau
14

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

for r in function():
    # process items
Brandon
fuente
2
Estoy completamente de acuerdo con usted en ese punto; sin embargo, la función no es mía, y solo la estoy usando.
artdanil 02 de
2
@artdanil: Entonces, podría incluir esa función en una que escriba que funcione como la que Brandon Corfman está pensando.
quamrana 02 de
24
lo que plantea la pregunta: ¿debería el contenedor usar un if o un intento de manejar el caso no iterable?
jcdyer 03 de
@quamrana, que es exactamente lo que estoy tratando de hacer. Entonces, como señaló @jcd, la pregunta es sobre cómo debo manejar la situación en la función de contenedor.
artdanil 03 de
12

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

¿Puedo suponer que "sin valor devuelto" significa que el valor devuelto es Ninguno? En caso afirmativo, o si el "sin valor" es falso booleano, puede hacer lo siguiente, ya que su código esencialmente trata "sin 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.

tzot
fuente
4

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

Dave Kirby
fuente
4

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

Mirar antes de saltar es preferible en este caso. Con el enfoque de excepción, un TypeError podría ocurrir en cualquier parte del cuerpo del bucle y quedaría atrapado y desechado, lo que no es lo que desea y dificultará la depuración.

(Sin embargo, estoy de acuerdo con Brandon Corfman: devolver None por 'no hay elementos' en lugar de una lista vacía está roto. Es un hábito desagradable de los codificadores Java que no deberían verse en Python. O Java).

bobince
fuente
4

En general, la impresión que tengo es que las excepciones deben reservarse para circunstancias excepcionales. Si resultse espera que el nunca esté vacío (pero podría estarlo, si, por ejemplo, un disco falla, etc.), el segundo enfoque tiene sentido. Si, por otro lado, un vacío resultes perfectamente razonable en condiciones normales, probarlo con una ifdeclaración 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
Managu
fuente
Este es un ejemplo de la diferencia entre los enfoques que menciona Tim Pietzcker: el primero es LBYL, el segundo es EAFP
Managu
Solo una nota rápida que ++no funciona en python, úsela en su += 1lugar.
tgray 02 de
3

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

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

Como puede ver, es bastante feo. No lo sugiero, pero debe mencionarse por completo.

AFoglia
fuente
En mis ojos, encontrar errores de tipo intencionalmente siempre es un mal estilo de codificación. Un desarrollador debe saber qué esperar y estar preparado para manejar esos valores sin excepciones. Cada vez que una operación hizo lo que se suponía que debía hacer (desde el punto de vista del programador) y los resultados esperados son una lista o None, siempre verifique el resultado con is Noneo is not None. Por otro lado, también es un mal estilo generar Excepciones para obtener resultados legítimos. Las excepciones son para las cosas inesperadas. Ejemplo: str.find()devuelve -1 si no se encuentra nada, porque la búsqueda en sí misma se completó sin errores.
Bachsau
1

En lo que respecta al rendimiento, usar el bloque try para el código que normalmente no genera excepciones es más rápido que usar la instrucción if cada vez. Entonces, la decisión depende de la probabilidad de casos excepcionales.

Grayger
fuente
-5

Como regla general, nunca debe usar try / catch ni ninguna excepción para controlar el flujo. Aunque la iteración detrás de escena se controla mediante el aumento de StopIterationexcepciones, aún debe preferir su primer fragmento de código al segundo.

ilowe
fuente
77
Esto es cierto en Java. Python abraza a EAFP con bastante fuerza.
fengb 02 de
3
Sigue siendo una práctica extraña. Los cerebros de la mayoría de los programadores están conectados para saltar sobre EAFP en mi experiencia. Terminan preguntándose por qué se seleccionó cierto camino. Dicho esto, hay un momento y un lugar para romper cada regla.
ilowe 02 de