¿Cómo puedo entender la cláusula `else` de los bucles Python?

191

Es probable que muchos programadores de Python ignoren que la sintaxis de los whilebucles y forbucles incluye una else:cláusula opcional :

for val in iterable:
    do_something(val)
else:
    clean_up()

El cuerpo de la elsecláusula es un buen lugar para ciertos tipos de acciones de limpieza, y se ejecuta en la terminación normal del bucle: es decir, al salir del bucle returnu breakomitir la elsecláusula; saliendo después de que un lo continueejecuta. Sé esto solo porque lo busqué (una vez más), porque nunca puedo recordar cuándoelse se ejecuta la cláusula.

¿Siempre? En "falla" del bucle, como su nombre indica? En la terminación regular? ¿Incluso si se sale del bucle con return? Nunca puedo estar completamente seguro sin buscarlo.

Culpo mi persistente incertidumbre a la elección de la palabra clave: encuentro elseincreíblemente poco nemónica para esta semántica. Mi pregunta no es "por qué se usa esta palabra clave para este propósito" (que probablemente votaría para cerrar, aunque solo después de leer las respuestas y comentarios), sino cómo puedo pensar en la elsepalabra clave para que su semántica tenga sentido, y yo por lo tanto puede recordarlo?

Estoy seguro de que hubo una buena cantidad de discusión sobre esto, y puedo imaginar que la elección se hizo por coherencia con la cláusula de la trydeclaración else:(que también tengo que buscar), y con el objetivo de no agregar a la lista de Las palabras reservadas de Python. Quizás las razones para elegir elseaclararán su función y la harán más memorable, pero busco conectar el nombre con la función, no después de una explicación histórica per se.

Las respuestas a esta pregunta , que mi pregunta se cerró brevemente como un duplicado, contienen muchas historias interesantes. Mi pregunta tiene un enfoque diferente (cómo conectar la semántica específica de elsecon la elección de la palabra clave), pero creo que debería haber un enlace a esta pregunta en alguna parte.

alexis
fuente
23
¿Qué tal "si queda algo para repetir ... más"
OneCricketeer
44
Creo que puedes recordarlo ahora después de escribir esta pregunta :)
Jasper
11
los elsemedios, básicamente, "si la condición de continuidad falla". En un bucle for tradicional, la condición de continuación es típicamente i < 42, en cuyo caso, puede ver esa parte comoif i < 42; execute the loop body; else; do that other thing
njzk2
1
Todo esto es cierto, y especialmente me gusta la respuesta de drawoc, pero otra cosa a considerar es que otra cosa es una palabra clave disponible que también tiene sentido sintáctico. Puede saber probar / excepto y quizás probar / excepto / finalmente, pero también tiene otra cosa : ejecute este código si no ocurrió ninguna excepción. Lo cual es, por cierto, no es lo mismo que insertar este código bajo la cláusula try: el manejo de excepciones se usa mejor cuando se apunta de manera restringida. Entonces, si bien tiene sentido conceptual, según varias respuestas aquí, creo que también es reutilización de palabras clave en el juego, ejecute esto bajo ciertas condiciones .
JL Peyret
1
@Falanwe, hay una diferencia cuando se sale del código break. El caso de uso canónico es cuando el bucle busca algo y se rompe cuando lo encuentra. El elsese ejecuta solo si no se encuentra nada.
alexis

Respuestas:

212

(Esto está inspirado en la respuesta de @Mark Tolonen).

Una ifdeclaración ejecuta su elsecláusula si su condición se evalúa como falsa. De manera idéntica, un whilebucle ejecuta la cláusula else si su condición se evalúa como falsa.

Esta regla coincide con el comportamiento que describió:

  • En la ejecución normal, el ciclo while se ejecuta repetidamente hasta que la condición se evalúa como falsa y, por lo tanto, al salir naturalmente del ciclo se ejecuta la cláusula else.
  • Cuando ejecuta una breakinstrucción, sale del bucle sin evaluar la condición, por lo que la condición no puede evaluar como falsa y nunca ejecuta la cláusula else.
  • Cuando ejecuta una continuedeclaración, evalúa la condición nuevamente y hace exactamente lo que normalmente haría al comienzo de una iteración de bucle. Por lo tanto, si la condición es verdadera, sigue en bucle, pero si es falsa, ejecuta la cláusula else.
  • Otros métodos para salir del bucle, como por ejemplo return, no evalúan la condición y, por lo tanto, no ejecutan la cláusula else.

forlos bucles se comportan de la misma manera. Simplemente considere la condición como verdadera si el iterador tiene más elementos, o falso en caso contrario.

drawoc
fuente
8
Esta es una excelente respuesta. Trate sus bucles como una serie de declaraciones elif y el comportamiento else expondrá su lógica natural.
Nomenator
1
También me gusta esta respuesta, pero no es una analogía con una serie de elifdeclaraciones. Hay una respuesta que sí, y tiene un voto a favor neto.
alexis
2
bueno, no exactamente, un ciclo while podría hacer que la condición se cumpla False justo antes de que se ejecute break, en cuyo caso elseno funcionaría pero la condición es False. Del mismo modo con forbucles puede breaken el último elemento.
Tadhg McDonald-Jensen
36

Es mejor pensarlo de esta manera: el elsebloque siempre se ejecutará si todo va bien en el forbloque anterior de modo que llegue al agotamiento.

Justo en este contexto significará no exception, no break, no return. Cualquier declaración que secuestre el control forprovocará que se omita el elsebloqueo.


Se encuentra un caso de uso común cuando se busca un elemento en un iterable, para el cual la búsqueda se cancela cuando se encuentra el elemento o "not found"se levanta / imprime una bandera a través del siguiente elsebloque:

for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")  

A continuehace control no secuestro de for, por lo que el control procederá a la elsedespués de la forse agota.

Moses Koledoye
fuente
20
Suena muy bien ... pero entonces esperarías elseque se ejecute una cláusula cuando las cosas no salen bien, ¿no? Ya me estoy confundiendo de nuevo ...
alexis
Tengo que estar en desacuerdo con usted en "Técnicamente, no es [semánticamente similar a todos los demás else]", ya que elsese ejecuta cuando ninguna de las condiciones en el ciclo for se evalúa como Verdadero, como lo demuestro en mi respuesta
Tadhg McDonald- Jensen
@ TadhgMcDonald-Jensen También puede romper el ciclo en un False. Entonces, la pregunta de cómo forse rompe depende del caso de uso.
Moses Koledoye
Así es, estoy pidiendo una forma de relacionar de alguna manera lo que sucede con el significado en inglés de "else" (que de hecho se refleja en otros usos de elsepython). Usted proporciona un buen resumen intuitivo de lo que elsehace, @Moses, pero no de cómo podríamos asociar este comportamiento con "else". Si se utilizara una palabra clave diferente (por ejemplo, nobreakcomo se menciona en esta respuesta a una pregunta relacionada), sería más fácil entenderlo.
alexis
1
Realmente no tiene nada que ver con "las cosas van bien". El resto se ejecuta puramente cuando la condición if/ whilese evalúa como falsa o no fortiene elementos. breakexiste el bucle contenedor (después del else). continueretrocede y evalúa la condición del bucle nuevamente.
Mark Tolonen
31

¿Cuándo ifejecuta un else? Cuando su condición es falsa. Es exactamente lo mismo para el while/ else. Por lo tanto, puede pensar en while/ elsecomo solo un ifque sigue ejecutando su verdadera condición hasta que evalúe falso. A breakno cambia eso. Simplemente salta del bucle contenedor sin evaluación. El elsesolo se ejecuta si la evaluación de la condición if/ whilees falsa.

El fores similar, excepto que su condición falsa está agotando su iterador.

continuey breakno se ejecutan else. Esa no es su función. El breaksale del bucle que contiene. La continueva de nuevo a la parte superior del bucle que contiene, en donde se evalúa la condición de bucle. Es el acto de evaluar if/ whilea falso (o forno tiene más elementos) que se ejecuta elsey no de otra manera.

Mark Tolonen
fuente
1
Lo que usted dice suena muy sensato, pero agrupar las tres condiciones de terminación, "hasta que [la condición] sea Falsa o se rompa / continúe", está mal: Crucialmente, la elsecláusula se ejecuta si se sale del bucle continue(o normalmente), pero no si salimos con break. Estas sutilezas son la razón por la que estoy tratando de asimilar lo que elseatrapa y lo que no.
alexis
44
@alexis sí, necesitaba aclarar allí. Editado continuar no ejecuta lo demás, pero regresa a la parte superior del ciclo que luego puede evaluar como falso.
Mark Tolonen
24

Esto es lo que esencialmente significa:

for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

Es una mejor manera de escribir este patrón común:

found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

La elsecláusula no se ejecutará si hay un returnporque returndeja la función, como debe ser. La única excepción a lo que puede estar pensando es finally, cuyo propósito es asegurarse de que siempre se ejecute.

continueno tiene nada especial que ver con este asunto. Hace que la iteración actual del ciclo finalice, lo que puede suceder que finalice todo el ciclo, y claramente en ese caso el ciclo no terminó con a break.

try/else es similar:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...
Alex Hall
fuente
20

Si piensa en sus bucles como una estructura similar a esta (algo pseudocódigo):

loop:
if condition then

   ... //execute body
   goto loop
else
   ...

podría tener un poco más de sentido. Un bucle es esencialmente solo una ifdeclaración que se repite hasta que la condición es false. Y este es el punto importante. El ciclo verifica su condición y ve que es falseasí, por lo tanto ejecuta el else(al igual que lo normal if/else) y luego el ciclo termina.

Observe que else solo se ejecuta cuando se verifica la condición . Eso significa que si sale del cuerpo del bucle en medio de la ejecución con, por ejemplo, a returno a break, dado que la condición no se vuelve a comprobar, el elsecaso no se ejecutará.

A, continuepor otro lado, detiene la ejecución actual y luego salta hacia atrás para verificar la condición del bucle nuevamente, por lo que se elsepuede alcanzar en este escenario.

Keiwan
fuente
Me gusta mucho esta respuesta, pero puedes simplificar: omite la endetiqueta y solo coloca el goto loopinterior del ifcuerpo. Tal vez incluso sangrienta al poner el ifen la misma línea que la etiqueta, y de repente se parece mucho a la original.
Bergi
@ Bergi Sí, creo que eso lo aclara un poco, gracias.
Keiwan
15

Mi momento de sorpresa con la elsecláusula del bucle fue cuando estaba viendo una charla de Raymond Hettinger , quien contó una historia sobre cómo pensó que debería haberse llamado nobreak. Eche un vistazo al siguiente código, ¿qué cree que haría?

for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

¿Qué adivinarías que hace? Bueno, la parte que dice nobreaksolo se ejecutará si breakno se golpea una declaración en el bucle.

nasser-sh
fuente
8

Por lo general, tiendo a pensar en una estructura de bucle como esta:

for item in my_sequence:
    if logic(item):
        do_something(item)
        break

Para parecerse mucho a un número variable de if/elifdeclaraciones:

if logic(my_seq[0]):
    do_something(my_seq[0])
elif logic(my_seq[1]):
    do_something(my_seq[1])
elif logic(my_seq[2]):
    do_something(my_seq[2])
....
elif logic(my_seq[-1]):
    do_something(my_seq[-1])

En este caso, la elsedeclaración en el bucle for funciona exactamente igual que la elsedeclaración en la cadena de elifs, solo se ejecuta si ninguna de las condiciones antes de evaluar a True. (o interrumpir la ejecución con returno una excepción) Si mi bucle no se ajusta a esta especificación, generalmente elijo dejar de usarlo for: elsepor la razón exacta por la que publicó esta pregunta: no es intuitivo.

Tadhg McDonald-Jensen
fuente
Correcto. Pero un ciclo se ejecuta varias veces, por lo que no está claro cómo quiere aplicar esto a un ciclo for. ¿Puedes aclarar?
alexis
@alexis He rehecho mi respuesta, creo que ahora está mucho más clara.
Tadhg McDonald-Jensen
7

Otros ya han explicado la mecánica de while/for...else, y la referencia del lenguaje Python 3 tiene la definición autorizada (ver while y for ), pero aquí está mi mnemónica personal, FWIW. Supongo que la clave para mí ha sido dividir esto en dos partes: una para comprender el significado de la elserelación con el bucle condicional y otra para comprender el control del bucle.

Creo que es más fácil comenzar por comprender while...else:

whiletienes más elementos, haz cosas, elsesi te quedas sin hacer esto

La for...elsemnemónica es básicamente la misma:

forcada elemento, haz cosas, pero elsesi se te acaba, haz esto

En ambos casos, el else parte solo se alcanza una vez que no hay más elementos para procesar, y el último elemento se ha procesado de manera regular (es decir, no breako return). A continuesimplemente regresa y ve si hay más elementos. Mi mnemotécnico para estas reglas se aplica a ambos whiley for:

cuando break ing o returning, no hay nada elseque hacer,
y cuando digo continue, eso es "vuelta al comienzo" para ti

- con "loop back to start" que significa, obviamente, el inicio del ciclo donde verificamos si hay más elementos en el iterable, en lo que respecta al else respecta, continuerealmente no juega ningún papel en absoluto.

Fabian Fagerholm
fuente
44
Me 'd sugieren que podría ser mejorada diciendo que el objetivo habitual de un bucle de / lo demás es examinar los elementos hasta que haya encontrado lo que busca y quiere dejar , o se quedará sin artículos. El "else" existe para manejar la parte "te quedas sin elementos (sin haber encontrado lo que estabas buscando)".
supercat
@supercat: Podría ser, pero no sé cuáles son los usos más comunes. También elsepodría usarse para hacer algo cuando simplemente haya terminado con todos los elementos. Los ejemplos incluyen escribir una entrada de registro, actualizar una interfaz de usuario o indicar algún otro proceso que haya realizado. Cualquier cosa en realidad. Además, algunos fragmentos de código tienen el caso "exitoso" que termina breakdentro del bucle y elsese usa para manejar el caso de "error" en el que no encontró ningún elemento adecuado durante la iteración (tal vez eso era lo que estaba pensando ¿de?).
Fabian Fagerholm
1
El caso en el que estaba pensando era precisamente el caso donde el caso exitoso termina con un "descanso", y el "otro" maneja la falta de éxito. Si no hay "interrupción" dentro de un bucle, el código "else" también puede simplemente seguir el bucle como parte del bloque de cierre.
supercat
A menos que necesite distinguir entre el caso en el que el bucle atravesó todos los elementos iterables sin interrupción (y ese fue un caso exitoso) y el caso donde no lo hizo. Luego debe poner el código de "finalización" en el elsebloque del bucle , o realizar un seguimiento del resultado utilizando otros medios. Básicamente estoy de acuerdo, solo digo que no sé cómo las personas usan esta función y, por lo tanto, me gustaría evitar hacer suposiciones de si el elseescenario " maneja el caso exitoso" o el escenario " elsemaneja el caso fracasado" es más común. Pero tienes un buen punto, ¡así que comenta tu voto!
Fabian Fagerholm
7

En el desarrollo basado en pruebas (TDD), cuando se utiliza la premisa de prioridad de transformación paradigma , los bucles se tratan como una generalización de declaraciones condicionales.

Este enfoque combina bien con esta sintaxis, si considera solo declaraciones simples if/else(no elif):

if cond:
    # 1
else:
    # 2

generaliza a:

while cond:  # <-- generalization
    # 1
else:
    # 2

bien.

En otros idiomas, los pasos de TDD de un solo caso a casos con colecciones requieren más refactorización.


Aquí hay un ejemplo del blog 8thlight :

En el artículo vinculado en 8thlight blog, se considera el kata de Word Wrap: agregar saltos de línea a las cadenas (la svariable en los fragmentos a continuación) para que se ajusten a un ancho determinado (la lengthvariable en los fragmentos a continuación). En un punto, la implementación tiene el siguiente aspecto (Java):

String result = "";
if (s.length() > length) {
    result = s.substring(0, length) + "\n" + s.substring(length);
} else {
    result = s;
}
return result;

y la próxima prueba, que actualmente falla es:

@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
    }

Entonces tenemos un código que funciona condicionalmente: cuando se cumple una condición particular, se agrega un salto de línea. Queremos mejorar el código para manejar múltiples saltos de línea. La solución presentada en el artículo propone aplicar la transformación (if-> while) , sin embargo, el autor hace un comentario que:

Si bien los bucles no pueden tener elsecláusulas, debemos eliminar el elsecamino haciendo menos en el ifcamino. Nuevamente, esto es una refactorización.

lo que obliga a hacer más cambios en el código en el contexto de una prueba fallida:

String result = "";
while (s.length() > length) {
    result += s.substring(0, length) + "\n";
    s = s.substring(length);
}
result += s;

En TDD queremos escribir la menor cantidad de código posible para que las pruebas pasen. Gracias a la sintaxis de Python, es posible la siguiente transformación:

de:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

a:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s
BartoszKP
fuente
6

A mi modo de ver, se else:dispara cuando iteras más allá del final del ciclo.

Si usted breako returno raiseno lo hace iterate allá del final del bucle, se deja de Golden Retriever, y por lo tanto el else:no se quedará bloque. Si continuetodavía itera más allá del final del ciclo, ya que continuar simplemente salta a la siguiente iteración. No detiene el ciclo.

Winston Ewert
fuente
1
Me gusta eso, creo que estás haciendo algo. Se relaciona un poco con cómo se implementaba el bucle en los viejos tiempos antes de las palabras clave de bucle. (A saber: el cheque se colocó en la parte inferior del bucle, con gotola parte superior del éxito). Pero es una versión más corta de la respuesta mejor votada ...
alexis
@alexis, subjetiva, pero creo que es más fácil pensar en mi forma de expresarla.
Winston Ewert
En realidad estoy de acuerdo. Aunque solo sea porque es más conmovedor.
alexis
4

Piense en la elsecláusula como parte de la construcción del bucle; breakse separa por completo de la construcción del bucle y, por lo tanto, omiteelse cláusula.

Pero realmente, mi mapeo mental es simplemente que es la versión 'estructurada' del patrón C / C ++:

  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

Entonces, cuando lo encuentro for...elseo lo escribo yo mismo, en lugar de entenderlo directamente , lo traduzco mentalmente a la comprensión anterior del patrón y luego calculo qué partes del mapa de sintaxis de Python a qué partes del patrón.

(Pongo 'estructurado' entre comillas de miedo porque la diferencia no es si el código está estructurado o no, sino simplemente si hay palabras clave y gramática dedicadas a la estructura particular)


fuente
1
¿Dónde está el else? Si te referías a la done:etiqueta como proxy o else:, creo que la tienes exactamente al revés.
alexis
@alexis El código 'else' completaría el '...' inmediatamente antes de la done:etiqueta. La correspondencia general es, quizás, mejor dicho así: Python tiene la elseconstrucción -on-loop para que pueda expresar este patrón de flujo de control sin él goto.
zwol
Hay otras formas de ejecutar este patrón de flujo de control, por ejemplo, estableciendo un indicador. Eso es lo que elseevita.
alexis
2

Si empareja elsecon for, podría ser confuso. No creo que la palabra clave haya elsesido una excelente opción para esta sintaxis, pero si la emparejas elsecon la ifque contiene break, puedes ver que realmente tiene sentido. elsees apenas útil si no hay una ifdeclaración anterior y creo que es por eso que el diseñador de sintaxis eligió la palabra clave.

Déjame demostrarlo en lenguaje humano.

forcada persona en un grupo de sospechosos ifcualquiera es el criminal de breakla investigación. elsereportar falla.

bombas
fuente
1

Desde mi punto de vista, la clave es considerar el significado de continuemás que else.

Las otras palabras clave que menciona se rompen del bucle (salen anormalmente) mientras continueque no lo hace, simplemente omite el resto del bloque de código dentro del bucle. El hecho de que pueda preceder a la terminación del bucle es incidental: la terminación se realiza de la manera normal mediante la evaluación de la expresión condicional del bucle.

Entonces solo necesita recordar que la elsecláusula se ejecuta después de la terminación normal del bucle.

Bob Sammers
fuente
0
# tested in Python 3.6.4
def buy_fruit(fruits):
    '''I translate the 'else' below into 'if no break' from for loop '''
    for fruit in fruits:
        if 'rotten' in fruit:
            print(f'do not want to buy {fruit}')
            break
    else:  #if no break
        print(f'ready to buy {fruits}')


if __name__ == '__main__':
    a_bag_of_apples = ['golden delicious', 'honeycrisp', 'rotten mcintosh']
    b_bag_of_apples = ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
    buy_fruit(a_bag_of_apples)
    buy_fruit(b_bag_of_apples)

'''
do not want to buy rotten mcintosh
ready to buy ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
'''
Por la corriente
fuente