utiliza - ¿Cómo puedo entender la cláusula `else` de los bucles Python?
for python español (14)
Muchos programadores de Python probablemente no son conscientes de que la sintaxis de los bucles while y
for
loops incluye una opción opcional
else:
cláusula:
for val in iterable:
do_something(val)
else:
clean_up()
El cuerpo de la cláusula
else
es un buen lugar para ciertos tipos de acciones de limpieza, y se ejecuta en la terminación normal del ciclo: es decir, al salir del ciclo con
return
o
break
salta la cláusula
else
;
salir después de
continue
ejecuta.
Sé esto solo porque lo
busqué
(una vez más), porque nunca puedo recordar
cuándo
se ejecuta la cláusula
else
.
¿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
else
increí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 palabra clave
else
para que su semántica tenga sentido, y Por lo tanto, puedo recordarlo?
Estoy seguro de que hubo una buena cantidad de discusión sobre esto, y me imagino que la elección se hizo por coherencia con la declaración de
try
else:
cláusula (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
else
aclarará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
else
con la elección de la palabra clave), pero creo que debería haber un enlace a esta pregunta en alguna parte.
¿Cuándo un
if
ejecuta a
else
?
Cuando su condición es falsa.
Es exactamente lo mismo para el
while
/
else
.
Por lo tanto, puede pensar en
while
/
else
como un
if
que sigue ejecutando su verdadera condición hasta que evalúe false.
Un
break
no cambia eso.
Simplemente salta del bucle contenedor sin evaluación.
El
else
solo se ejecuta si la
evaluación de
la condición
if
/
while
es falsa.
El
for
es similar, excepto que su condición falsa está agotando su iterador.
continue
y
break
no ejecutar
else
.
Esa no es su función.
La
break
sale del bucle contenedor.
La
continue
vuelve a la parte superior del bucle contenedor, donde se evalúa la condición del bucle.
Es el acto de evaluar
if
/
while
es falso (o
for
no tiene más elementos) lo que se ejecuta de
else
y de ninguna otra manera.
(Esto está inspirado en la respuesta de @Mark Tolonen).
Una instrucción
if
ejecuta su cláusula
else
si su condición se evalúa como falsa.
De manera idéntica, un ciclo while 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 declaración de
break
, sale del bucle sin evaluar la condición, por lo que la condición no se puede evaluar como falsa y nunca ejecuta la cláusula else. -
Cuando ejecuta una instrucción de
continue
, 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
return
, no evalúan la condición y, por lo tanto, no ejecutan la cláusula else.
for
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.
A mi modo de ver, de lo
else:
dispara cuando iteras más allá del final del bucle.
Si se
break
return
o
raise
, no itera más allá del final del ciclo, se detiene de inmediato y, por lo tanto, lo
else:
bloque no se ejecutará.
Si
continue
, todavía itera más allá del final del ciclo, ya que continuar solo salta a la siguiente iteración.
No detiene el ciclo.
A mi modo de verlo, la clave es considerar el significado de
continue
lugar de
else
.
Las otras palabras clave que menciona se rompen del bucle (salen anormalmente) mientras que
continue
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 ciclo es incidental: la terminación se realiza de la manera normal mediante la evaluación de la expresión condicional del ciclo.
Entonces solo necesita recordar que la cláusula
else
se ejecuta después de la terminación normal del bucle.
En el desarrollo impulsado por pruebas (TDD), cuando se usa el paradigma Premisa de prioridad de transformación , 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 el blog 8thlight, se considera el kata de Word Wrap: agregar saltos de línea a las cadenas (la variable
s
en los fragmentos a continuación) para que se ajusten a un ancho determinado (la variable de
length
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 cláusulas
else
, debemos eliminar la rutaelse
haciendo menos en la rutaif
. 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:
desde:
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
Es mejor pensarlo de esta manera: el bloque
else
siempre
se ejecutará si todo va
bien
en el bloque anterior
for
que se agote.
Justo
en este contexto significará sin
exception
, sin
break
, sin
return
.
Cualquier declaración que secuestre el control de
for
hará que se omita el bloque
else
.
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 se levanta / imprime un indicador
"not found"
través del siguiente bloque
else
:
for items in basket:
if isinstance(item, Egg):
break
else:
print("No eggs in basket")
Una
continue
no secuestra el control de
for
,
for
lo que el control pasará a
else
después
for
se agote
for
.
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 cláusula
else
no se ejecutará si hay un
return
porque el
return
deja 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.
continue
no 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 una
break
.
try/else
es similar:
try:
...
except:
...
if there was an exception:
pass
else:
...
Mi momento de sorpresa con la clá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
nobreak
solo se ejecutará si no se golpea una declaración de
break
en el bucle.
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 lo
else
en relació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
:
while
tenga más artículos, haga cosas, de loelse
, si se acaba, haga esto
El
for...else
mnemónico es básicamente el mismo:
for
cada artículo, haz cosas, pero si te quedas sin nada, haz esto
En ambos casos, la parte
else
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, sin
break
o
return
).
Una
continue
simplemente regresa y ve si hay más elementos.
Mi mnemotécnico para estas reglas se aplica tanto a
while
como
for
:
cuando se
break
oreturn
, no hay nadaelse
que hacer,
y cuando digocontinue
, 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, por lo que, en lo que respecta a lo
else
,
continue
realmente no juega ningún papel en absoluto.
Piense en la cláusula
else
como parte de la construcción del bucle;
break
rompe por completo la construcción del bucle y, por lo tanto, omite la cláusula
else
.
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 me encuentro
for...else
o 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.
(Puse ''estructurado'' entre comillas de miedo porque la diferencia no es si el código es estructurado o no, sino simplemente si hay palabras clave y gramática dedicadas a la estructura particular)
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 sentencias
if/elif
:
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 instrucción
else
en el bucle for funciona exactamente igual que la instrucción
else
en la cadena de
elif
s, solo se ejecuta si ninguna de las condiciones antes de evaluar a True.
(o interrumpir la ejecución con
return
o una excepción) Si mi bucle no se ajusta a esta especificación, por lo general, elijo dejar de usarlo
for: else
por la razón exacta por la que publicó esta pregunta: no es intuitivo.
Si intentas emparejar
else
con
for
en tu mente, podría ser confuso.
No creo que la palabra clave
else
una gran opción para esta sintaxis, pero si la emparejas con
break
, puedes ver que realmente tiene sentido.
Déjame demostrarlo en lenguaje humano.
for
cada persona en un grupo de sospechosos,if
alguien es el criminal,break
la investigación.else
informe de falla.
else
es apenas útil si de todos modos no hubo
break
en el ciclo
for
.
Si piensa en sus bucles como una estructura similar a esta (algo de pseudocódigo):
loop:
if condition then
... //execute body
goto loop
else
...
podría tener un poco más de sentido.
Un ciclo es esencialmente solo una declaración
if
que se repite hasta que la condición sea
false
.
Y este es el punto importante.
El bucle verifica su condición y ve que es
false
, por lo tanto ejecuta el
else
(como
if/else
un
if/else
normal) y luego el bucle está hecho.
Observe que lo
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, un
return
o una
break
, ya que la condición no se vuelve a verificar, el caso de lo
else
no se ejecutará.
Una
continue
por otro lado, detiene la ejecución actual y luego salta hacia atrás para verificar la condición del bucle nuevamente, razón por la cual se puede alcanzar el
else
en este escenario.
# 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'']
''''''