Python usando excepciones para el flujo de control considerado malo?

Todo bien,

He visto esto varias veces en el pasado, pero más recientemente con mi pregunta aquí . Por lo tanto, tengo curiosidad por qué este es el caso, en Python, porque los generadores usan excepciones para indicar el final de los datos.

Si esto es tan malo para todos los que usan python, ¿por qué el lenguaje lo incluye en lo que se consideran estructuras de control fundamentales? Para aquellos que quieran leer el PEP relevante haz clic aquí .

Porque terminar el generador no es un evento común (sé que siempre ocurrirá, pero solo ocurre una vez ). Lanzar la excepción se considera caro. Si un evento tendrá éxito el 99% del tiempo y fallará el 1%, usar try / except puede ser mucho más rápido que verificar si está bien acceder a esos datos (es más fácil pedir perdón que permiso).

También hay un sesgo en su contra ya que los bloques try / except utilizados así pueden ser muy difíciles de entender. El control de flujo puede ser difícil de seguir, mientras que un si / es más sencillo. El bash / excepto significa que tiene que seguir el control de flujo de las declaraciones dentro del bash y dentro de las funciones que llama (ya que pueden lanzar la excepción y pueden propagarse hacia arriba. Se evalúa la statement.

Hay ocasiones en las que usar try / except es correcto y otras veces si tiene más sentido. También hay costos de rendimiento asociados con cada uno de ellos. Considerar:

a =  if key in a: print a[key] 

contra

 a =  try: print a[key] except KeyError: pass 

La primera será más rápida si la clave no existe dentro de una y solo será ligeramente más lenta (casi imperceptible) si existe. El segundo será más rápido si la clave existe, pero será mucho más lento si no existe. Si la clave casi siempre existe, vas con la segunda. De lo contrario, el primero funciona mejor.

EDITAR: Solo una pequeña cosa para agregar sobre Python try / excepto que ayuda mucho con uno de los problemas de legibilidad.

Considere la lectura de un archivo.

 f = None try: f = open(filename, 'r') ... do stuff to the file ... except (IOError, OSError): # I can never remember which one of these Python throws... ... handle exception ... finally: if f: f.close() 

Ahora, cualquier cosa que se do stuff to the file puede generar una excepción y lo capturaremos. Comúnmente, tratas de mantener el menor código posible en el bash por este motivo. Python tiene una cláusula else opcional para el bash que solo se ejecutará si el bash se ejecuta hasta completar sin golpear una excepción.

 f = None try: f = open(filename, 'r') except (IOError, OSError): pass else: ... do stuff to the file ... finally: if f: f.close() 

En este caso, no tendrías ninguno de los problemas de legibilidad ya que solo una statement está en el bash; es una llamada a la función de la biblioteca estándar de python y está detectando solo excepciones específicas.

El uso de bloques de try para el control de flujo todo el tiempo podría producir un código como este:

 try: # stuff try: if userCondition: throw NeedToDoSomethingElseException try: # stuff except NeedToDoSomethingElseException: # other stuff except NeedToDoSomethingElseException: # other stuff except NeedToDoSomethingElseException: # other stuff 

Dejando de lado el rendimiento, esto no es muy elegante. Entonces, a veces es perfectamente apropiado usar try , pero no para todo.

Debido a que las excepciones se generan en la stack, son apropiadas para algunos casos en los que desea que el código que utiliza otro código pueda detectar la excepción. Por ejemplo, considere el caso en el que desea crear un iterador que usa parte de otro iterador más “manualmente”, es valioso tener una excepción que puede capturar desde la parte superior de la stack e insertar su propia lógica.