El curioso comportamiento del 'and' y el 'or' en Python

Cuando en Python te preguntan por la manera más corta de escribir un bloque if … else … como el siguiente:

if a:
    return b
else:
    return c

¿Cuál manera se te viene a la mente?

Seguramente luego de pensarlo un poco la manera que has escogido es la siguiente:

b if a else c

¿Y si te digo que hay otra manera incluso más corta?
Así es, podemos escribir el mismo bloque de la siguiente manera:

a and b or c

Pero, ¿Por qué es esto posible?

Esto es debido al comportamiento que tienen los operadores booleanos en Python.
Para decirlo en pocas palabras, en Python los operadores booleanos se ejecutan de la misma manera que esperas, sólo que en lugar de retornar valores booleanos retornan alguno de los valores que se está comparando.

Antes de explicarlo con más detalle recordemos brevemente como se evalúan las expresiones en Python en contextos booleanos (representación booleana).

gráfica 1

Ahora sí, para explicarlo de manera más elaborada, empecemos primero con el operador and elaborando algunos casos:

  1. 'a' and 'b' >> 'b'
  2. '' and 'b' >> ''
  3. 'a' and '' >> ''
  4. '' and 0 >> ''
  5. 'a' and 'b' and 'c' >> 'c'

1) Al usar el operador and las expresiones se evalúan de izquierda a derecha, para este caso primero se evalúa 'a' y como es verdadero, entonces el resultado de la operación dependerá del segundo término, en este caso 'b' el cual es verdadero, por lo tanto, la expresión es verdadera y se retorna 'b'.
2) En este caso se evalúa '', como esta expresión es falsa y estamos en un and se retorna '', dicho de otro modo, el and evalúa las expresiones y retorna la primera expresión falsa en el caso de existir alguna.
3) Este caso es similar al primero, se evalúa de izquierda a derecha, como 'a' es verdadero entonces se retorna la segunda expresión la cual es '', lo cual equivale a que esta operación resultó en falso.
4) Este caso es exactamente como el segundo, fíjense que no importa si el valor que siga después de la primera expresión falsa es falso también, sigue retornándose el primer valor.
5) Y para terminar con un ejemplo más largo, pero igual de simple, se evalúan las expresiones y como 'a' y 'b' son verdaderas, el resultado de la operación dependerá del tercer valor el cual es 'c'.

Ahora vamos con el operador or elaborando algunos casos similares:

  1. 'a' or 'b' >> 'a'
  2. '' or 'b' >> 'b'
  3. 'a' or '' >> 'a'
  4. '' or [] or {} >> {}
  5. 'a' or 'b' or 'c' >> 'a'

1) Para el caso del operador or se evalúa igual de izquierda a derecha, cuando se encuentra un valor equivalente a verdadero, se retorna ese valor inmediatamente. Para este caso 'a' es verdadero, así que se retorna 'a'.
2) En este caso, como '' es falso, entonces el resultado de la operación dependerá de la segunda expresión, la cual es 'b' que es verdadera, entonces se retorna 'b'.
3) En este caso como 'a' es verdadera se retorna inmediatamente, es importante resaltar que a diferencia del and en este caso, ni siquiera se evalúan las expresiones restantes, puedes comprobarlo creando una función que imprima algo y retorne una expresión que en un contexto booleano sea verdadero y podrás notar como no se imprime nada.
4) Para este caso, se resuelve el primer or el cual depende de la segunda expresión porque la primera es falsa, entonces al evaluar el segundo or ocurre de nuevo lo mismo, lo que nos lleva a que el resultado de la operación completa depende del último miembro el cual es falso, en este caso se retorna entonces {}.
5) Este caso es exactamente como el tercero, se retorna 'a' y ni siquiera se evalúan las siguientes expresiones.

Ahora que entendemos la naturaleza de los operadores and y or podemos pasar a analizar el estilo de if … else … utilizando el truco and-or. Tomemos entonces los siguientes casos:

1) 'a' and 'b' or 'c' >> 'b'
2) '' and 'b' or 'c' >> 'c'

1) La operación se evalúa de izquierda a derecha, así que primero se resuelve el and. Como 'a' es verdadero, el resultado del and depende del segundo miembro, en este caso 'b' que es verdadero, el and devuelve entonces 'b', se procede a evaluar el or. Como 'b' es verdadero, se retorna 'b' inmediatamente.
2) Para este caso se evalúa nuevamente de izquierda a derecha, al resolver el and se obtiene como resultado '' dado que es la primera expresión falsa encontrada, se procede a evaluar el or. Como '' es falso, el resultado de la operación depende del segundo miembro, en este caso 'c', entonces el resultado de toda la expresión es 'c'.

Estas expresiones son la representación exacta de una estructura if … else …, pruébalo tú mismo, escribe ambas expresiones utilizando los bloques if … else … tradicionales en Python.

Pero...

No obstante, vale la pena hacer una importante aclaración. Escribir los bloques if … else … de esta manera, es una manera poco legible de programar, incluso si es para un caso muy sencillo, esto puede hacer doler la cabeza a tus compañeros cuando estén revisando tu código. Por otro lado, el truco and-or tiene algunos casos en los que falla, lee este artículo para más información.

Pero calma, no quiero que sientas que has perdido tu tiempo al llegar hasta aquí, la intención de este artículo es explicar el comportamiento de los operadores booleanos en Python y este comportamiento puede ser utilizado en otros casos para crear código muy bonito y legible como el siguiente:

def schedule_appointment(appointment, person, date=None):
    self.date = date or datetime.datetime.now()
    # appointment scheduling …

Supongamos que tenemos una clase para la cual, uno de sus métodos programa citas para alguna persona en determinada fecha y que queremos que asigne una fecha si es pasada mediante el parámetro o que agregue la fecha actual de lo contrario.

Dado que no se pueden llamar funciones en los valores por defecto de un parámetro, una buena solución es inicializar el parámetro a una expresión que en un contexto booleano represente falso (En este caso None) y en el cuerpo de la función aprovechar el comportamiento del operador or para asignar condicionalmente el valor de la variable. Si lo piensan, este truco resulta en una pieza de código bastante legible y bastante elegante.

Bueno, eso es todo por ahora, espero que les haya gustado este artículo y que resulte útil para alguien, si no me he hecho entender en algún punto por favor exprésenmelo en los comentarios.

¡Saludos!