¿Por qué Python usa 'else' después de los bucles for y while?
Entiendo cómo funciona esta construcción:
for i in range(10):
print(i)
if i == 9:
print("Too big - I'm giving up!")
break
else:
print("Completed successfully")
Pero no entiendo por qué else
se usa como palabra clave aquí, ya que sugiere que el código en cuestión solo se ejecuta si el for
bloque no se completa, ¡que es lo opuesto a lo que hace! No importa cómo lo piense, mi cerebro no puede pasar sin problemas de la for
declaración al else
bloque. Para mí, continue
o continuewith
tendría más sentido (y estoy tratando de entrenarme para leerlo como tal).
Me pregunto cómo los programadores de Python leen esta construcción en su cabeza (o en voz alta, si lo desea). ¿Quizás me falta algo que haría que dichos bloques de código sean más fácilmente descifrables?
Esta pregunta trata sobre la decisión de diseño subyacente , es decir, por qué es útil poder escribir este código. Consulte también la cláusula Else en la declaración while de Python para la pregunta específica sobre lo que significa la sintaxis .
Una construcción común es ejecutar un bucle hasta que se encuentre algo y luego salir del bucle. El problema es que si salgo del bucle o el bucle termina, necesito determinar qué caso ocurrió. Un método es crear una bandera o una variable de tienda que me permita hacer una segunda prueba para ver cómo se salió del bucle.
Por ejemplo, supongamos que necesito buscar en una lista y procesar cada elemento hasta encontrar un elemento marcado y luego detener el procesamiento. Si falta el elemento de bandera, entonces se debe generar una excepción.
Usando la construcción Python for
... else
tienes
for i in mylist:
if i == theflag:
break
process(i)
else:
raise ValueError("List argument missing terminal flag.")
Compare esto con un método que no utiliza este azúcar sintáctico:
flagfound = False
for i in mylist:
if i == theflag:
flagfound = True
break
process(i)
if not flagfound:
raise ValueError("List argument missing terminal flag.")
En el primer caso, raise
está estrechamente vinculado al bucle for con el que trabaja. En el segundo la unión no es tan fuerte y pueden introducirse errores durante el mantenimiento.
Es una construcción extraña incluso para los programadores Python experimentados. Cuando se usa junto con bucles for, básicamente significa "buscar algún elemento en el iterable; de lo contrario, si no se encuentra ninguno, haga ...". Como en:
found_obj = None
for obj in objects:
if obj.key == search_key:
found_obj = obj
break
else:
print('No object found.')
Pero cada vez que veas esta construcción, una mejor alternativa es encapsular la búsqueda en una función:
def find_obj(search_key):
for obj in objects:
if obj.key == search_key:
return obj
O utilice una lista por comprensión:
matching_objs = [o for o in objects if o.key == search_key]
if matching_objs:
print('Found {}'.format(matching_objs[0]))
else:
print('No object found.')
No es semánticamente equivalente a las otras dos versiones, pero funciona bastante bien en código que no es crítico para el rendimiento, donde no importa si se itera toda la lista o no. Otros pueden no estar de acuerdo, pero yo personalmente evitaría usar los bloques for-else o while-else en el código de producción.
Véase también [Python-ideas] Resumen de for...else hilos
Hay una excelente presentación de Raymond Hettinger, titulada Transforming Code into Beautiful, Idiomatic Python , en la que aborda brevemente la historia de la for ... else
construcción. La sección relevante es "Distinguir múltiples puntos de salida en bucles" que comienza a las 15:50 y continúa durante unos tres minutos. Aquí están los puntos destacados:
- La
for ... else
construcción fue ideada por Donald Knuth como reemplazo para ciertosGOTO
casos de uso; - Reutilizar la
else
palabra clave tenía sentido porque "es lo que usó Knuth, y la gente sabía, en ese momento, que todas [for
las declaraciones] tenían incrustado unif
yGOTO
debajo, y esperaban elelse
;" - En retrospectiva, debería haberse llamado "sin interrupción" (o posiblemente "sin interrupción"), y así no sería confuso.*
Entonces, si la pregunta es "¿Por qué no cambian esta palabra clave?" entonces Cat Plus Plus probablemente dio la respuesta más precisa : en este punto, sería demasiado destructivo para el código existente para ser práctico. Pero si la pregunta que realmente te haces es por qué else
se reutilizó en primer lugar, bueno, aparentemente parecía una buena idea en ese momento.
Personalmente, me gusta el compromiso de comentar # no break
en línea dondequiera que else
, a simple vista, se pueda confundir con algo que pertenece al bucle. Es razonablemente claro y conciso. Esta opción recibe una breve mención en el resumen que Bjorn vinculó al final de su respuesta:
Para completar, debo mencionar que con un ligero cambio en la sintaxis, los programadores que quieran esta sintaxis pueden tenerla ahora mismo:
for item in sequence: process(item) else: # no break suite
* Cita adicional de esa parte del vídeo: "Al igual que si llamaramos a lambda makefunction, nadie preguntaría: '¿Qué hace lambda?'"
Para hacerlo simple, puedes pensar en ello así;
- Si encuentra el
break
comando en elfor
bucle,else
no se llamará la pieza. - Si no encuentra el
break
comando en elfor
bucle,else
se llamará a la pieza.
En otras palabras, si la iteración del bucle for no se "rompe" con break
, else
se llamará a la parte.
Porque no querían introducir una nueva palabra clave en el idioma. Cada uno roba un identificador y causa problemas de compatibilidad con versiones anteriores, por lo que suele ser el último recurso.