if-statement español - ¿Por qué python usa 'else' después de los bucles for y while?




in range (15)

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?


Answers

Es una construcción extraña incluso para codificadores Python experimentados. Cuando se usa junto con los bucles for, 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 el código crítico de no 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 salirse 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 necesito 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 for ... else 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.


Estoy de acuerdo, es más como un 'Elif no [condición (s) que levanta la ruptura]'.

Sé que este es un hilo 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 forma en que la 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)

Entonces, esencialmente, el "else" en un bucle es realmente un "elif ..." donde '...' es (1) sin interrupción, lo que es equivalente 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
----------

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


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 declaración 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')

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 quieras hacer trampa y saltarte algo que encontraste fue roto. Es decir, continúe con el bucle aunque haya encontrado 5 como roto, de lo contrario la declaración se imprimirá. 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 :-)


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 .

Es tan simple.


Aquí hay otro caso de uso idiomático además de buscar. Digamos que quería esperar a que se cumpla una condición, por ejemplo, un puerto que esté abierto 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()

Lo leí algo así como:

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


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 programadores 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 docs

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 declaración 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 declaración de prueba y las excepciones, consulte Manejo de excepciones.

La instrucción continue, 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

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 ideado 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 usaba, 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 sintaxis, los programadores que desean esta sintaxis 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 a Lambda makefunction, nadie preguntaría: '¿Qué hace lambda?'"


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 declaración de ruptura ".

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


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

Descarga Twisted y mira el código fuente. Emplean algunas técnicas bastante avanzadas.





python if-statement for-loop for-else