¿Por qué es “excepto: pasar” una mala práctica de progtwigción?

A menudo veo comentarios sobre otras preguntas de desbordamiento de stack sobre cómo se desalienta el uso de except: pass . ¿Por qué es esto malo? A veces simplemente no me importa cuáles son los errores, y quiero simplemente continuar con el código.

 try: something except: pass 

¿Por qué está usando un bloque de except: pass mal? ¿Qué lo hace malo? ¿Es el hecho de que pass un error o de que except algún error?

Como ha adivinado correctamente, hay dos caras: detectar cualquier error al no especificar el tipo de excepción después de la except , y simplemente pasarlo sin realizar ninguna acción.

Mi explicación es “un poco” más larga, así que tl; dr se desglosa en esto:

  1. No atrapes ningún error . Siempre especifique de qué excepciones está preparado para recuperarse y solo capture esas.
  2. Trate de evitar pasar a excepción de los bloques . A menos que se desee explícitamente, esto no suele ser una buena señal.

Pero vamos a entrar en detalles:

No atrapes ningún error

Cuando usa un bloque de try , generalmente hace esto porque sabe que existe la posibilidad de que se lance una excepción. Como tal, también tiene una idea aproximada de qué se puede romper y qué excepción se puede lanzar. En tales casos, atrapa una excepción porque puede recuperarse positivamente de ella. Eso significa que está preparado para la excepción y tiene algún plan alternativo que seguirá en caso de esa excepción.

Por ejemplo, cuando le pide al usuario que ingrese un número, puede convertir la entrada usando int() que podría generar un ValueError . Puede recuperarlo fácilmente simplemente pidiéndole al usuario que lo intente de nuevo, por lo que capturar el ValueError y ValueError al usuario otra vez sería un plan apropiado. Un ejemplo diferente sería si desea leer alguna configuración de un archivo, y ese archivo no existe. Debido a que es un archivo de configuración, es posible que tenga alguna configuración predeterminada como alternativa, por lo que el archivo no es exactamente necesario. Entonces, la captura de un FileNotFoundError y la simple aplicación de la configuración predeterminada sería un buen plan aquí. Ahora, en ambos casos, tenemos una excepción muy específica que esperamos y tenemos un plan igualmente específico para recuperarnos. Como tal, en cada caso, solo explícitamente except esa cierta excepción.

Sin embargo, si tuviéramos que atraparlo todo , entonces, además de las excepciones de las que estamos preparados para recuperarnos, también existe la posibilidad de que obtengamos excepciones que no esperábamos y de las que no podemos recuperarnos; o no debería recuperarse de.

Tomemos el ejemplo del archivo de configuración de arriba. En caso de que falte un archivo, solo aplicamos nuestra configuración predeterminada, y en un momento posterior podríamos decidir guardar automáticamente la configuración (por lo que la próxima vez, el archivo existe). Ahora imagine que obtenemos un IsADirectoryError o un PermissionError lugar. En tales casos, probablemente no queremos continuar; aún podríamos aplicar nuestra configuración predeterminada, pero luego no podremos guardar el archivo. Y es probable que el usuario también tenga una configuración personalizada, por lo que es probable que no se desee utilizar los valores predeterminados. Por lo tanto, nos gustaría informarle al usuario de inmediato, y probablemente abortar la ejecución del progtwig también. Pero eso no es algo que queramos hacer en algún lugar profundo dentro de una pequeña parte del código; Esto es algo de importancia a nivel de aplicación, por lo que debe manejarse en la parte superior, así que deje que la excepción crezca.

Otro ejemplo simple también se menciona en el documento de expresiones de Python 2 . Aquí, existe un error tipográfico simple en el código que hace que se rompa. Como NameError todas las excepciones, también NameError y SyntaxError . Ambos son errores que nos suceden a todos mientras progtwigmos; y ambos son errores que no queremos incluir al enviar el código. Pero debido a que también los detectamos, ni siquiera sabremos que ocurrieron allí y perderemos ayuda para depurarlos correctamente.

Pero también hay excepciones más peligrosas para las cuales no estamos preparados. Por ejemplo, SystemError suele ser algo que ocurre raramente y que realmente no podemos planear; significa que hay algo más complicado, algo que probablemente nos impide continuar con la tarea actual.

En cualquier caso, es muy improbable que esté preparado para todo en una parte del código a pequeña escala, por lo que realmente es donde debe detectar las excepciones para las que está preparado. Algunas personas sugieren al menos capturar Exception ya que no incluirá elementos como SystemExit y KeyboardInterrupt que, por diseño , terminan su aplicación, pero yo diría que esto todavía es demasiado inespecífico. Solo hay un lugar donde personalmente acepto la Exception captura o cualquier excepción, y eso es en un único controlador de excepción de nivel de aplicación global que tiene el único propósito de registrar cualquier excepción para la que no estemos preparados. De esa manera, aún podemos retener tanta información sobre excepciones inesperadas, que luego podemos usar para extender nuestro código para manejarlas explícitamente (si podemos recuperarnos de ellas) o, en caso de un error, crear casos de prueba para asegurarnos de que No volverá a suceder. Pero, por supuesto, eso solo funciona si alguna vez detectamos las excepciones que ya esperábamos, por lo que las que no esperábamos naturalmente explotarán.

Trate de evitar pasar excepto bloques.

Al capturar explícitamente una pequeña selección de excepciones específicas, hay muchas situaciones en las que estaremos bien al no hacer nada. En tales casos, solo con except SomeSpecificException: pass está bien. Sin embargo, la mayoría de las veces este no es el caso, ya que es probable que necesitemos algún código relacionado con el proceso de recuperación (como se mencionó anteriormente). Esto puede ser, por ejemplo, algo que vuelva a intentar la acción, o para configurar un valor predeterminado en su lugar.

Sin embargo, si ese no es el caso, por ejemplo, porque nuestro código ya está estructurado para que se repita hasta que tenga éxito, entonces simplemente aprobar es lo suficientemente bueno. Tomando nuestro ejemplo de arriba, podríamos pedirle al usuario que ingrese un número. Como sabemos que a los usuarios les gusta no hacer lo que les pedimos, podríamos ponerlo en un bucle en primer lugar, para que se vea así:

 def askForNumber (): while True: try: return int(input('Please enter a number: ')) except ValueError: pass 

Debido a que seguimos intentando hasta que no se produce ninguna excepción, no necesitamos hacer nada especial en el bloque de excepción, por lo que está bien. Pero, por supuesto, se podría argumentar que al menos queremos mostrarle al usuario algún mensaje de error para decirle por qué tiene que repetir la entrada.

Sin embargo, en muchos otros casos, solo pasar una except es una señal de que no estábamos realmente preparados para la excepción que estamos detectando. A menos que esas excepciones sean simples (como ValueError o TypeError ), y la razón por la que podemos aprobar es obvia, intente evitar simplemente la aprobación. Si realmente no hay nada que hacer (y está absolutamente seguro de ello), entonces considere agregar un comentario por qué ese es el caso; de lo contrario, expanda el bloque de excepción para incluir realmente algún código de recuperación.

except: pass

El peor ofensor es la combinación de ambos. Esto significa que estamos captando cualquier error, aunque no estamos preparados para ello y tampoco hacemos nada al respecto. Al menos desea registrar el error y también es probable que vuelva a subirlo para que finalice la aplicación (es poco probable que pueda continuar como siempre después de un error de memoria). El simple hecho de pasar no solo mantendrá la aplicación algo viva (dependiendo del lugar donde se detecte, por supuesto), sino que también desechará toda la información, lo que hace que sea imposible descubrir el error, lo cual es especialmente cierto si usted no es quien lo descubre.


Así que la conclusión es: Cumpla solo las excepciones que realmente esperas y de las que estás preparado para recuperarte; todos los demás son probablemente errores que deberías corregir, o algo para lo que no estás preparado. Pasar excepciones específicas está bien si realmente no necesitas hacer algo al respecto. En todos los demás casos, es solo un signo de presunción y de ser perezoso. Y definitivamente quieres arreglar eso.

El principal problema aquí es que ignora todos y cualquier error: sin memoria, la CPU se está quemando, el usuario quiere parar, el progtwig quiere salir, Jabberwocky está matando a los usuarios.

Esto es demasiado. En tu cabeza, estás pensando “quiero ignorar este error de red”. Si algo inesperado sale mal, entonces su código continúa silenciosamente y se rompe en formas completamente impredecibles que nadie puede depurar.

Es por eso que debe limitarse a ignorar específicamente solo algunos errores y dejar pasar el rest.

Ejecutar su pseudo código literalmente no da ningún error:

 try: something except: pass 

como si fuera una pieza de código perfectamente válida, en lugar de lanzar un NameError . Espero que esto no sea lo que quieres.

¿Por qué es “excepto: pasar” una mala práctica de progtwigción?

¿Por qué es esto malo?

 try: something except: pass 

Esto detecta todas las excepciones posibles, incluidas GeneratorExit , KeyboardInterrupt y SystemExit , que son excepciones que probablemente no tiene intención de detectar. Es lo mismo que atrapar BaseException .

 try: something except BaseException: pass 

Las versiones más antiguas de la documentación dicen :

Dado que cada error en Python genera una excepción, el uso de except: puede hacer que muchos errores de progtwigción parezcan problemas de tiempo de ejecución, lo que dificulta el proceso de depuración.

Jerarquía de excepciones de Python

Si atrapa una clase de excepción principal, también captura todas sus clases secundarias. Es mucho más elegante capturar solo las excepciones que está preparado para manejar.

Aquí está la jerarquía de excepciones de Python 3: ¿realmente quieres atraparlos a todos ?:

 BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning 

No hagas esto

Si está utilizando esta forma de manejo de excepciones:

 try: something except: # don't just do a bare except! pass 

Entonces no podrás interrumpir tu bloque de something con Ctrl-C. Su progtwig pasará por alto todas las excepciones posibles dentro del bloque de código de try .

Aquí hay otro ejemplo que tendrá el mismo comportamiento indeseable:

 except BaseException as e: # don't do this either - same as bare! logging.info(e) 

En su lugar, intente capturar solo la excepción específica que sabe que está buscando. Por ejemplo, si sabe que puede obtener un error de valor en una conversión:

 try: foo = operation_that_includes_int(foo) except ValueError as e: if fatal_condition(): # You can raise the exception if it's bad, logging.info(e) # but if it's fatal every time, raise # you probably should just not catch it. else: # Only catch exceptions you are prepared to handle. foo = 0 # Here we simply assign foo to 0 and continue. 

Explicación adicional con otro ejemplo.

Es posible que lo esté haciendo porque ha estado buscando en la Web y ha estado recibiendo, por ejemplo, un UnicodeError , pero debido a que ha utilizado la captura de Excepciones más amplia, su código, que puede tener otras fallas fundamentales, intentará ejecutarse hasta el final, desperdiciando ancho de banda, tiempo de procesamiento, desgaste de su equipo, falta de memoria, recostackción de datos de basura, etc.

Si otras personas le piden que complete para que puedan confiar en su código, entiendo que me siento obligado a manejar todo. Pero si está dispuesto a fallar ruidosamente a medida que se desarrolla, tendrá la oportunidad de corregir problemas que podrían aparecer de forma intermitente, pero que serían errores costosos a largo plazo.

Con un manejo de errores más preciso, el código puede ser más robusto.

 >>> import this 

El zen de Python, por Tim Peters

Lo bello es mejor que lo feo.
Explícito es mejor que implícito.
Lo simple es mejor que lo complejo.
Complejo es mejor que complicado.
Plano es mejor que nested.
Lo escaso es mejor que lo denso.
La legibilidad cuenta.
Los casos especiales no son lo suficientemente especiales para romper las reglas.
Aunque la practicidad supera a la pureza.
Los errores nunca deben pasar en silencio.
A menos que sea explícitamente silenciado.
Ante la ambigüedad, rechace la tentación de adivinar.
Debe haber una, y preferiblemente solo una, obvia forma de hacerlo.
Aunque esa forma puede no ser obvia al principio a menos que seas holandés.
Ahora es mejor que nunca.
Aunque nunca es a menudo mejor que ahora.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede ser una buena idea.
Los espacios de nombres son una gran idea, ¡hagamos más de ellos!

Entonces, aquí está mi opinión. Cada vez que encuentre un error, debe hacer algo para manejarlo, es decir, escribirlo en el archivo de registro o algo más. Al menos, le informa que solía haber un error.

Debe usar al menos, except Exception: para evitar la captura de excepciones del sistema como SystemExit o KeyboardInterrupt . Aquí hay un enlace a los documentos.

En general, debe definir explícitamente las excepciones que desea capturar, para evitar la captura de excepciones no deseadas. Debes saber qué excepciones ignoras .

Primero, viola dos principios del Zen de Python :

  • Explícito es mejor que implícito
  • Los errores nunca deben pasar en silencio.

Lo que significa es que intencionalmente haces que tu error pase en silencio. Además, no sabe qué evento ocurrió exactamente, porque except: pass detectará cualquier excepción.

En segundo lugar, si tratamos de abstraernos del Zen de Python y hablamos en términos de cordura, deberías saber que el uso de except:pass te deja sin conocimiento y control en tu sistema. La regla de oro es plantear una excepción, si ocurre un error, y tomar las medidas apropiadas. Si no sabe de antemano, qué acciones deberían ser, al menos registre el error en algún lugar (y mejor vuelva a generar la excepción):

 try: something except: logger.exception('Something happened') 

Pero, por lo general, si intenta detectar alguna excepción, ¡probablemente esté haciendo algo mal!

La construcción except:pass esencialmente silencia todas y cada una de las condiciones excepcionales que surgen mientras se ejecuta el código cubierto en el bloque try: .

Lo que hace esta mala práctica es que generalmente no es lo que realmente quieres. Más a menudo, aparece una condición específica que desea silenciar, y except:pass es un instrumento demasiado contundente. Terminará el trabajo, pero también ocultará otras condiciones de error que probablemente no haya anticipado, pero que quizás quiera tratar de alguna otra manera.

Lo que hace que esto sea particularmente importante en Python es que, según los modismos de este lenguaje, las excepciones no son necesariamente errores . A menudo se usan de esta manera, por supuesto, al igual que en la mayoría de los idiomas. Pero Python, en particular, los ha usado ocasionalmente para implementar una ruta de salida alternativa desde algunas tareas de código que no es realmente parte del caso de ejecución normal, pero aún se sabe que aparece de vez en cuando e incluso puede esperarse en la mayoría de los casos. SystemExit ya se ha mencionado como un ejemplo antiguo, pero el ejemplo más común en la actualidad puede ser StopIteration . El uso de excepciones de esta manera causó mucha controversia, especialmente cuando los iteradores y los generadores se introdujeron por primera vez en Python, pero finalmente la idea prevaleció.

La razón # 1 ya se ha declarado: oculta errores que no esperaba.

(# 2) – Hace que su código sea difícil de leer y entender para otros. Si detecta una excepción FileNotFoundException cuando intenta leer un archivo, es bastante obvio para otro desarrollador qué funcionalidad debe tener el bloque ‘catch’. Si no especifica una excepción, entonces necesita comentarios adicionales para explicar lo que debe hacer el bloque.

(# 3) – Demuestra progtwigción perezosa. Si usa el try / catch genérico, indica que no entiende los posibles errores de tiempo de ejecución en su progtwig o que no sabe qué excepciones son posibles en Python. La captura de un error específico muestra que usted comprende tanto su progtwig como el rango de errores que Python lanza. Es más probable que esto haga que otros desarrolladores y revisores de códigos confíen en su trabajo.

Entonces, ¿qué salida produce este código?

 fruits = [ 'apple', 'pear', 'carrot', 'banana' ] found = False try: for i in range(len(fruit)): if fruits[i] == 'apple': found = true except: pass if found: print "Found an apple" else: print "No apples in list" 

Ahora imagine el try , except bloque es cientos de líneas de llamadas a una jerarquía de objetos complejos, y se llama en el centro del árbol de llamadas del gran progtwig. Cuando el progtwig sale mal, ¿dónde empiezas a buscar?

En general, puede clasificar cualquier error / excepción en una de tres categorías :

  • Fatal : No es tu culpa, no puedes evitarlos, no puedes recuperarte de ellos. Ciertamente, no debe ignorarlos y continuar, y dejar su progtwig en un estado desconocido. Solo deja que el error termine tu progtwig, no hay nada que puedas hacer.

  • Boneheaded : Su propio fallo, probablemente debido a un error de supervisión, error o progtwigción. Deberías arreglar el error. De nuevo, seguramente no debes ignorar y continuar.

  • Exógeno : puede esperar estos errores en situaciones excepcionales, como el archivo no encontrado o la conexión terminada . Debes manejar explícitamente estos errores, y solo estos.

En todos los casos, except: pass solo dejará su progtwig en un estado desconocido, donde puede causar más daño.

En pocas palabras, si se produce una excepción o error, algo está mal. Puede que no sea algo muy incorrecto, pero crear, lanzar y capturar errores y excepciones por el simple hecho de usar sentencias goto no es una buena idea, y rara vez se hace. El 99% de las veces, hubo un problema en alguna parte.

Los problemas deben ser tratados. Al igual que en la vida, en la progtwigción, si dejas los problemas solos y tratas de ignorarlos, no desaparecen solos muchas veces; en cambio se hacen más grandes y se multiplican. Para evitar que un problema crezca en ti y te vuelva a atacar en la carretera, puedes: 1) eliminarlo y limpiar el desorden después, o 2) contenerlo y limpiar el desorden después.

Simplemente ignorar las excepciones y los errores y dejarlos así es una buena manera de experimentar memory leaks, conexiones de bases de datos sobresalientes, lockings innecesarios de permisos de archivos, etc.

En raras ocasiones, el problema es tan minúsculo, trivial y, aparte de necesitar un try … catch block, es autónomo , por lo que realmente no hay problema que limpiar después. Estas son las únicas ocasiones en que esta mejor práctica no se aplica necesariamente. En mi experiencia, esto generalmente significa que lo que sea que esté haciendo el código es básicamente insignificante y algo así como los bashs de rebash o los mensajes especiales no valen la complejidad ni retienen el hilo.

En mi empresa, la regla es casi siempre hacer algo en un bloque catch, y si no haces nada, siempre debes colocar un comentario con una buena razón por la que no. Nunca debe pasar o dejar un bloque de captura vacío cuando haya algo que hacer.

En mi opinión, los errores tienen una razón para aparecer, que mi sonido es estúpido, pero así es como es. La buena progtwigción solo genera errores cuando tienes que manejarlos. Además, como leí hace algún tiempo, “la statement de aprobación es una statement que muestra que el código se insertará más adelante”, por lo que si desea tener una statement de excepción vacía, siéntase libre de hacerlo, pero para un buen progtwig habrá ser una parte faltante. Porque no manejas las cosas que deberías tener. Las excepciones que aparecen le dan la oportunidad de corregir los datos de entrada o de cambiar su estructura de datos para que estas excepciones no vuelvan a ocurrir (pero en la mayoría de los casos (las excepciones de red, las excepciones de entrada generales) las excepciones indican que las siguientes partes del progtwig no se ejecutarán correctamente. Por ejemplo, una NetworkException puede indicar una conexión de red rota y el progtwig no puede enviar / recibir datos en los siguientes pasos del progtwig.

Pero el uso de un bloque de aprobación solo para un bloque de ejecución es válido, porque aún se diferencia entre los tipos de excepciones, por lo que si coloca todos los bloques de excepción en uno, no estará vacío:

 try: #code here except Error1: #exception handle1 except Error2: #exception handle2 #and so on 

Se puede reescribir de esa manera:

 try: #code here except BaseException as e: if isinstance(e, Error1): #exception handle1 elif isinstance(e, Error2): #exception handle2 ... else: raise 

Por lo tanto, incluso varios bloques de excepción con declaraciones de aprobación pueden generar un código, cuya estructura maneja tipos especiales de excepciones.

Todos los comentarios planteados hasta ahora son válidos. Siempre que sea posible, debe especificar qué excepción exacta desea ignorar. Siempre que sea posible, debe analizar qué causó la excepción y solo ignorar lo que quería ignorar, y no el rest. Si la excepción hace que la aplicación se “cuelgue espectacularmente”, entonces, porque es mucho más importante saber lo inesperado que sucedió cuando sucedió, que ocultar que el problema ocurrió alguna vez.

Con todo lo dicho, no tome ninguna práctica de progtwigción como un elemento primordial. Esto es estúpido. Siempre existe el momento y el lugar para hacer el locking de ignorar todas las excepciones.

Otro ejemplo de importancia idiota es el uso del operador goto . Cuando estaba en la escuela, nuestro profesor nos enseñó a goto operador goto solo para mencionar que nunca lo usarás, NUNCA. No creas que la gente te dice que nunca se debe usar xyz y que no puede haber un escenario cuando sea útil. Siempre hay

Manejar errores es muy importante en la progtwigción. Usted necesita mostrar al usuario lo que salió mal. En muy pocos casos puedes ignorar los errores. Esto es muy mala práctica de progtwigción.

Como aún no se ha mencionado, es mejor usar el estilo contextlib.suppress :

 with suppress(FileNotFoundError): os.remove('somefile.tmp') 

Observe que en el ejemplo proporcionado, el estado del progtwig sigue siendo el mismo, ya sea que se produzca o no la excepción. Es decir, somefile.tmp siempre se vuelve inexistente.