¿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 la palabra clave aquí, ya que sugiere que el código en cuestión solo se ejecuta si el bloque for no se completa, ¡que es lo opuesto a lo que hace! No importa cómo lo piense, mi cerebro no puede progresar sin problemas desde la instrucción for hasta el bloque else . Para mí, continue o continuewith tendría más sentido (y estoy tratando de entrenarme para leerlo como tal).

Me pregunto cómo los codificadores de Python leen esta construcción en su cabeza (o en voz alta, si lo desea). ¿Tal vez me esté perdiendo algo que haría que tales bloques de código sean más fácilmente descifrables?

Es una construcción extraña incluso para codificadores Python experimentados. Cuando se usa junto con for-loops, básicamente significa “encontrar algún elemento en el iterable, de lo contrario, si no se encontró ninguno …”. 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 vea este constructo, una alternativa mejor 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 use una lista de 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 lo suficientemente bien en código no crítico para el rendimiento donde no importa si se repite la lista completa o no. Otros pueden estar en desacuerdo, pero personalmente evitaría usar los bloques for-else o while-else en el código de producción.

Ver también [Python-ideas] Resumen de los hilos de for … else

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 si el bucle termina, debo determinar qué caso sucedió. Un método es crear una marca o variable de tienda que me permita hacer una segunda prueba para ver cómo se salió del bucle.

Por ejemplo, suponga que debo buscar en una lista y procesar cada elemento hasta que se encuentre un elemento de marca y luego detener el procesamiento. Si falta el elemento de marca, entonces se debe generar una excepción.

Usando el Python forelse construye 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 usa 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, el raise está vinculado estrechamente al bucle for con el que funciona. En el segundo, la unión no es tan fuerte y se pueden introducir errores durante el mantenimiento.

Hay una excelente presentación de Raymond Hettinger, titulada Transforming Code into Beautiful, Idiomatic Python , en la que aborda brevemente la historia de la construcción for ... else . La sección relevante es “Distinguir múltiples puntos de salida en bucles” a partir de las 15:50 y continuando durante aproximadamente tres minutos. Aquí están los puntos altos:

  • El constructo for ... else fue diseñado por Donald Knuth como un reemplazo para ciertos casos de uso de GOTO ;
  • Reutilizar la palabra clave else tenía sentido porque “es lo que Knuth usó, y la gente sabía, en ese momento, todos [ for declaraciones] habían incrustado un if y GOTO debajo, y esperaban la else “;
  • En retrospectiva, debería haber sido llamado “no break” (o posiblemente “nobreak”), y entonces 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 que el código existente sea práctico. Pero si la pregunta que realmente estás preguntando es por qué else fue reutilizada 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 donde cualquier else podría confundirse, a simple vista, como pertenecer dentro del bucle. Es razonablemente claro y conciso. Esta opción obtiene 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 syntax, los progtwigdores que desean esta syntax pueden tenerla ahora mismo:

 for item in sequence: process(item) else: # no break suite 

* Cita de bonificación de esa parte del video: “Al igual que si llamáramos la función de la función de lambda , nadie preguntaría ‘¿Qué hace la lambda?'”

Porque no querían introducir una nueva palabra clave al lenguaje. Cada uno roba un identificador y causa problemas de compatibilidad hacia atrás, por lo que generalmente es un último recurso.

La forma más fácil que encontré para “obtener” lo que hizo el / el otro lo hizo, y lo que es más importante, cuándo usarlo, fue concentrarse en el lugar en el que salta la statement de ruptura. La construcción For / else es un solo bloque. La ruptura salta del bloque y, por lo tanto, salta “sobre” la cláusula else. Si el contenido de la cláusula else simplemente siguiera la cláusula for, nunca se saltaría, por lo que habría que proporcionar la lógica equivalente colocándola en un if. Esto se ha dicho antes, pero no exactamente con estas palabras, por lo que puede ayudar a alguien más. Intente ejecutar el siguiente fragmento de código. Estoy incondicionalmente a favor del comentario ‘sin interrupción’ para mayor claridad.

 for a in range(3): print(a) if a==4: # change value to force break or not break else: #no break +10 for whoever thought of this decoration print('for completed OK') print('statement after for loop') 

Lo leí algo así como:

Si aún está en las condiciones para ejecutar el bucle, haga cosas, o haga algo más.

Creo que la documentación tiene una gran explicación de otra cosa , continúa

[…] se ejecuta cuando el bucle termina al agotarse la lista (con for) o cuando la condición se vuelve falsa (con while), pero no cuando el bucle termina con una statement de ruptura “.

Fuente: Documentos de Python 2: Tutorial sobre el flujo de control

Para hacerlo simple, puedes pensar en eso así;

  • Si encuentra el comando break en el bucle for , no se llamará la parte else .
  • Si no encuentra el comando break en el bucle for , se llamará la parte else .

En otras palabras, si para la iteración de bucle no se “rompe” con break , se llamará la parte else .

Ya que la parte técnica ha sido bastante respondida, mi comentario está relacionado con la confusión que produce esta palabra clave reciclada .

Siendo Python un lenguaje de progtwigción muy eloquent , el mal uso de una palabra clave es más notorio. La palabra clave else describe perfectamente parte del flujo de un árbol de decisión, “si no puede hacer esto, (sino) haga eso”. Está implícito en nuestro propio idioma.

En su lugar, el uso de esta palabra clave con while y for declaraciones crea confusión. La razón, nuestra carrera como progtwigdores nos ha enseñado que la statement else reside dentro de un árbol de decisión; su scope lógico , un contenedor que condicionalmente devuelve una ruta a seguir. Mientras tanto, las declaraciones de bucle tienen un objective explícito figurativo para alcanzar algo. El objective se cumple después de continuas iteraciones de un proceso.

if / else indicar un camino a seguir . Los bucles siguen un camino hasta que se completa el “objective” .

El problema es que else es una palabra que define claramente la última opción en una condición. Python y Human Language comparten la semántica de la palabra. Pero la palabra else en Human Language nunca se usa para indicar las acciones que alguien o algo tomará después de que se complete algo. Se utilizará si, en el proceso de completarlo, surge un problema (más como una statement de ruptura ).

Al final, la palabra clave permanecerá en Python. Está claro que fue un error, más claro cuando cada progtwigdor intenta crear una historia para entender su uso como un dispositivo mnemotécnico. Me hubiera encantado si hubieran elegido la palabra clave. Creo que esta palabra clave encaja perfectamente en ese flujo iterativo, la recompensa después del bucle.

Se parece a la situación que tiene un niño después de seguir cada paso para armar un juguete: ¿Y ENTONCES, qué papá?

Lo leí como “Cuando el iterable se agote por completo, y la ejecución esté a punto de pasar a la siguiente statement después de finalizar el for , se ejecutará la cláusula else”. Por lo tanto, cuando la iteración se interrumpe por break , esto no se ejecutará.

Estoy de acuerdo, es más como un ‘Elif no [condición (s) que levanta el descanso]’.

Sé que este es un tema antiguo, pero estoy analizando la misma pregunta en este momento, y no estoy seguro de que alguien haya captado la respuesta a esta pregunta de la manera que lo entiendo.

Para mí, hay tres formas de “leer” el else en For... else o While... else , todas las cuales son equivalentes, son:

  1. else == if the loop completes normally (without a break or error)
  2. else == if the loop does not encounter a break
  3. else == else not (condition raising break) (presumiblemente existe tal condición, o usted no tendría un bucle)

Así que, esencialmente, el “else” en un bucle es realmente un “elif …” donde ‘…’ es (1) sin interrupción, lo que equivale a (2) NOT [condición (s) levantando ruptura].

Creo que la clave es que la else tiene sentido sin el ‘descanso’, por lo que un for...else incluye:

 for: do stuff conditional break # implied by else else not break: do more stuff 

Por lo tanto, los elementos esenciales de un bucle for...else son los siguientes, y los leería en un lenguaje sencillo como:

 for: do stuff condition: break else: # read as "else not break" or "else not condition" do more stuff 

Como han dicho los otros carteles, generalmente se genera una ruptura cuando puede localizar lo que su bucle está buscando, por lo que la else: convierte en “qué hacer si el elemento de destino no se encuentra”.

Ejemplo

También puede utilizar el manejo de excepciones, los descansos y los bucles todos juntos.

 for x in range(0,3): print("x: {}".format(x)) if x == 2: try: raise AssertionError("ASSERTION ERROR: x is {}".format(x)) except: print(AssertionError("ASSERTION ERROR: x is {}".format(x))) break else: print("X loop complete without error") 

Resultado

 x: 0 x: 1 x: 2 ASSERTION ERROR: x is 2 ---------- # loop not completed (hit break), so else didn't run 

Ejemplo

Ejemplo simple con una ruptura siendo golpeado.

 for y in range(0,3): print("y: {}".format(y)) if y == 2: # will be executed print("BREAK: y is {}\n----------".format(y)) break else: # not executed because break is hit print("y_loop completed without break----------\n") 

Resultado

 y: 0 y: 1 y: 2 BREAK: y is 2 ---------- # loop not completed (hit break), so else didn't run 

Ejemplo

Ejemplo simple donde no hay interrupciones, ninguna condición que genere una interrupción, y no se encuentran errores.

 for z in range(0,3): print("z: {}".format(z)) if z == 4: # will not be executed print("BREAK: z is {}\n".format(y)) break if z == 4: # will not be executed raise AssertionError("ASSERTION ERROR: x is {}".format(x)) else: print("z_loop complete without break or error\n----------\n") 

Resultado

 z: 0 z: 1 z: 2 z_loop complete without break or error ---------- 

La palabra clave else puede ser confusa aquí, y como muchas personas han señalado, algo como nobreak , notbreak es más apropiado.

Para entender for ... else ... lógicamente, compárelo con try...except...else , no if...else... , la mayoría de los progtwigdores de Python están familiarizados con el siguiente código:

 try: do_something() except: print("Error happened.") # The try block threw an exception else: print("Everything is find.") # The try block does things just find. 

Del mismo modo, piense en la break como un tipo especial de Exception :

 for x in iterable: do_something(x) except break: pass # Implied by Python's loop semantics else: print('no break encountered') # No break statement was encountered 

La diferencia es que python implica una except break y no se puede escribir, por lo que se convierte en:

 for x in iterable: do_something(x) else: print('no break encountered') # No break statement was encountered 

Sí, sé que esta comparación puede ser difícil y aburrida, pero aclara la confusión.

Los códigos en el bloque else instrucción se ejecutarán cuando el bucle for no se haya interrumpido.

 for x in xrange(1,5): if x == 5: print 'find 5' break else: print 'can not find 5!' #can not find 5! 

De los documentos: romper y continuar declaraciones, y otras cláusulas en los bucles

Las declaraciones de bucle pueden tener una cláusula más; se ejecuta cuando el bucle termina por agotamiento de la lista (con para) o cuando la condición se vuelve falsa (con while), pero no cuando el bucle termina con una statement de interrupción. Esto se ejemplifica en el siguiente bucle, que busca números primos:

 >>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print(n, 'equals', x, '*', n//x) ... break ... else: ... # loop fell through without finding a factor ... print(n, 'is a prime number') ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3 

(Sí, este es el código correcto. Mire detenidamente: la cláusula else pertenece al bucle for, no a la sentencia if).

Cuando se usa con un bucle, la cláusula else tiene más en común con la cláusula else de una sentencia try que con las sentencias if: la cláusula else de una sentencia try se ejecuta cuando no se produce una excepción, y la cláusula else de un bucle se ejecuta cuando no se produce una ruptura . Para obtener más información sobre la statement de prueba y las excepciones, consulte Gestión de excepciones.

La statement de continuación, también tomada de C, continúa con la siguiente iteración del bucle:

 >>> for num in range(2, 10): ... if num % 2 == 0: ... print("Found an even number", num) ... continue ... print("Found a number", num) Found an even number 2 Found a number 3 Found an even number 4 Found a number 5 Found an even number 6 Found a number 7 Found an even number 8 Found a number 9 

Podrías pensar en ello como, de else manera, en el rest de las cosas, o en otras cosas, que no se hicieron en el bucle.

Aquí hay una manera de pensar que no he visto a nadie más mencionar arriba:

Primero, recuerda que los bucles for son básicamente azúcar sintáctica alrededor de los bucles while. Por ejemplo, el bucle.

 for item in sequence: do_something(item) 

puede ser reescrito (aproximadamente) como

 item = None while sequence.hasnext(): item = sequence.next() do_something(item) 

Segundo, recuerde que los bucles while se repiten básicamente solo bloques if! Siempre puede leer un bucle while como “si esta condición es verdadera, ejecute el cuerpo, luego regrese y verifique nuevamente”.

Entonces, mientras / else tiene perfecto sentido: es exactamente la misma estructura que if / else, con la funcionalidad agregada de bucle hasta que la condición se vuelve falsa en lugar de solo revisar la condición una vez.

Y entonces / también tiene mucho sentido: porque todos los bucles for son solo azúcar sintáctica además de los bucles while, solo necesitas averiguar cuál es la condicional implícita del bucle while subyacente, y luego la otra parte corresponde a cuándo. la condición se vuelve falsa.

Aquí hay otro caso de uso idiomático además de la búsqueda. Digamos que quería esperar a que se cumpla una condición, por ejemplo, un puerto que se abra en un servidor remoto, junto con algún tiempo de espera. Entonces podrías utilizar un while...else construir así:

 import socket import time sock = socket.socket() timeout = time.time() + 15 while time.time() < timeout: if sock.connect_ex(('127.0.0.1', 80)) is 0: print('Port is open now!') break print('Still waiting...') else: raise TimeoutError() 
 for i in range(3): print(i) if i == 2: print("Too big - I'm giving up!") break; else: print("Completed successfully") 

“else” aquí es locamente simple, solo significa

1, “si for clause se completa”

 for i in range(3): print(i) if i == 2: print("Too big - I'm giving up!") break; if "for clause is completed": print("Completed successfully") 

Se está manejando para escribir declaraciones tan largas como “porque la cláusula está completa”, por lo que introducen “else”.

else aquí hay un si en su naturaleza.

2, sin embargo, ¿qué hay for clause is not run at all

 In [331]: for i in range(0): ...: print(i) ...: ...: if i == 9: ...: print("Too big - I'm giving up!") ...: break ...: else: ...: print("Completed successfully") ...: Completed successfully 

Así que es completamente enunciado una combinación lógica:

 if "for clause is completed" or "not run at all": do else stuff 

o ponlo de esta manera:

 if "for clause is not partially run": do else stuff 

o de esta manera:

 if "for clause not encounter a break": do else stuff 

Solo estaba tratando de darle sentido de nuevo a mí mismo. ¡Encontré que lo siguiente ayuda!

• Piense que la else está emparejada con el if dentro del bucle (en lugar de con la for ) – si se cumple la condición, entonces rompa el bucle, o haga esto – ¡excepto que es una else emparejada con múltiples if s!
• Si no, if se satisficiera en absoluto, haga lo else .
• El múltiplo if s también se puede considerar como if – ¡ elif s!

Las grandes respuestas son:

  • esto que explica la historia, y
  • Esto le da la cita correcta para facilitar su traducción / comprensión.

Mi nota aquí proviene de lo que Donald Knuth dijo una vez (lo siento, no puedo encontrar una referencia) de que hay una construcción donde while-else no se distingue de if-else, a saber (en Python):

 x = 2 while x > 3: print("foo") break else: print("boo") 

tiene el mismo flujo (excluyendo las diferencias de bajo nivel) como:

 x = 2 if x > 3: print("foo") else: print("boo") 

El punto es que if-else puede considerarse como azúcar sintáctica para while-else que tiene una ruptura implícita al final de su bloque if . La implicación opuesta es que el bucle while es una extensión de if , ya que solo es una verificación condicional repetida, es más común. Sin embargo, se queda corto cuando se considera if-else porque eso significaría else locking else en while-else se ejecutará cada vez que la condición sea falsa.

Para facilitar su comprensión, piense de esa manera:

Sin break , return , etc., el bucle termina solo cuando la condición ya no es verdadera (en el caso de que deba considerar el estilo C for bucles o traducirlos a while ) y, de lo else bloque se ejecuta cuando la condición es falsa.

Otra nota:

La break prematura, el return , etc., el bucle interno hace imposible que la condición se vuelva falsa porque la ejecución saltó fuera del bucle mientras que la condición era verdadera y nunca volvería a comprobarlo.

Supongamos que tenemos una función

 def broken(x) : return False if x==5 else True 

Lo que significa que solo 5 no está roto. Ahora en caso de quebrado nunca se evalúa con 5: –

 for x in range(4): if not broken(x) : break else: print("Everything broken... doom is upon us") 

Dará salida: –

 Everything broken... doom is upon us 

Donde cuando roto se evalúa con 5: –

 for x in range(6): if not broken(x) : break else: print("Everything broken... doom is upon us") 

No imprimirá nada. Así, indirectamente diciendo que hay al menos algo que no está roto.

Sin embargo, en caso de que quiera hacer trampa y omitir algo que encontró, se rompió. Es decir, continúe con el ciclo aunque haya encontrado 5 como roto, de lo contrario, la statement se seguirá imprimiendo. Es decir :-

 for x in range(6): if not broken(x) : continue else: print("Everything broken... doom is upon us") 

Imprimirá

 Everything broken... doom is upon us 

Espero que despeje la confusión en lugar de crear una nueva 🙂

Considero la estructura como para (si) A else B, y para (if) -else es un especial if-else , aproximadamente . Puede ayudar a entender otra cosa .

A y B se ejecutan a lo sumo una vez, que es lo mismo que la estructura if-else.

for (if) puede considerarse como un if especial, que hace un ciclo para tratar de cumplir la condición if. Una vez que se cumple la condición if , A y break ; Si no , b .