Saltar al contenido
IDE libre

Fundamentos

Elección entre muchas opciones en Python: elif, in y diccionarios

Cuándo usar una cadena de elif, el operador in o un diccionario para elegir entre muchas opciones en Python, y por qué MicroPython no tiene match/case.

Por Prof. Rodrigo Faustino · Actualizado el · 14 min de lectura

Métricas de este artículo

Palabras
2780
Caracteres
15.486
Tiempo de lectura
~14 min

Para elegir entre muchas opciones en Python hay tres herramientas, y la mejor depende de lo que compara la condición. Cuando el programa decide por rangos de valores, como una nota o una temperatura, la cadena de elif es la herramienta correcta. Cuando compara con dos o tres valores exactos, el operador in acorta la condición. Cuando necesita traducir muchos valores exactos en respuestas, como un mes en nombre o un código de error en mensaje, el diccionario hace el mismo trabajo con menos líneas y deja los datos en un solo lugar. MicroPython no tiene match ni case, así que esa es la lista completa de caminos en la ESP32.

El artículo anterior mostró el elif para rangos. Este muestra qué hacer cuando la escalera se vuelve demasiado larga, presenta el diccionario en el tamaño que la decisión necesita (la serie tiene un artículo entero sobre él más adelante) y explica por qué un código copiado de internet con match no corre en la placa.

Una escalera que solo crece

La calculadora con menú es el ejemplo clásico: el usuario elige una operación, y el programa hace la cuenta.

for op in ("+", "-", "*", "/", "%", "?"):
    a = 12
    b = 5
    if op == "+":
        r = a + b
    elif op == "-":
        r = a - b
    elif op == "*":
        r = a * b
    elif op == "/":
        r = a / b
    else:
        r = "operación inválida"
    print(a, op, b, "=", r)

La salida muestra 12 + 5 = 17, 12 - 5 = 7, 12 * 5 = 60, 12 / 5 = 2.4 y, para las dos últimas líneas, 12 % 5 = operación inválida y 12 ? 5 = operación inválida. El programa funciona, y el else final cumplió el papel que el artículo anterior le pidió: el ? no existe, y el % todavía no fue programado. Fíjate en el detalle del %: es una operación válida en Python, pero la escalera no tiene su peldaño, así que el mensaje dice "inválida" para una cuenta que el programa podría hacer. Cada operación nueva pide dos líneas más, y ninguna cuenta de cabeza garantiza que las comparaciones estén todas escritas bien.

Aquí la escalera todavía es pequeña. La incomodidad aparece cuando llega a doce peldaños, como en un programa que convierte el número del mes en nombre, y la decisión en cada peldaño es la misma: "¿el valor es igual a este?".

FastTeacherPara profesores

Del programa al semestre planificado

  • Plan de enseñanza y calendario montados solos
  • Diario de clases listo para pegar en el SIGA
  • Diapositivas y listas de ejercicios con solucionario
Conocer FastTeacher

Versión Community gratis · Windows

Rango o valor exacto

Un diagrama de flujo que parte de mirar la condición y abre tres caminos. Los rangos de valores, como nota mayor o igual a 9, llevan al elif. Dos o tres valores exactos, como x igual a 1, 2 o 3, llevan a elif o al operador in. Muchos valores exactos, como un código que se vuelve texto, llevan al diccionario. Una franja final resume: rango pide elif, valor exacto en muchos casos pide diccionario.
Para elegir la herramienta, mira qué compara la condición: rangos, pocos valores exactos o muchos valores exactos.

La pregunta que organiza la elección es qué compara la condición. Una nota de 0 a 10 tiene infinitos valores posibles (7, 7.5, 7.0001), y la decisión trata cada rango como un bloque. Ningún diccionario ayuda ahí, porque habría que listar todos los valores. Un mes, en cambio, solo puede ser 1, 2, 3 hasta 12, y cada valor tiene su respuesta.

  • Si las condiciones usan >, >=, < o <=, usa la cadena de elif, y el orden de los rangos importa, como en el artículo anterior.
  • Si las condiciones son solo == contra el mismo valor repetido, piensa en in o en un diccionario.

Un diccionario con rangos parece funcionar, y es la trampa que encuentran los alumnos. Para verlo, considera un diccionario en el que la nota 7 vale "B":

nota = 7
niveis = {10: "A", 9: "A", 8: "B", 7: "B"}
print(niveis.get(nota, "?"))
print(niveis.get(7.5, "?"))

La salida es B y ?. La nota 7 está en la lista, pero la 7.5 no, porque el diccionario solo conoce los valores que se escribieron en él. Para rangos, elif.

El operador in: varios valores, una pregunta

Cuando la misma respuesta vale para varios valores, la condición con or repite la variable en cada comparación:

x = 3
if x == 1 or x == 2 or x == 3:
    print("dentro")
if x in (1, 2, 3):
    print("dentro con in")
if x in [1, 2, 3]:
    print("dentro con lista")
c = "b"
if c in "abc":
    print("b está en abc")
print("bc" in "abc", "ac" in "abc")

La salida tiene dentro, dentro con in, dentro con lista, b está en abc y, en la última línea, True False. La pregunta x in (1, 2, 3) es "¿el valor de x aparece en esta secuencia?", y los paréntesis (una tupla) y los corchetes (una lista) sirven igualmente aquí. Con texto, in pregunta si un pedazo está dentro del otro: "bc" in "abc" es verdadero porque bc aparece en secuencia, y "ac" in "abc" es falso porque las dos letras no quedan pegadas.

El error más clásico con varios valores es escribir el or solo con la segunda mitad:

op = "%"
if op == "+" or "-":
    print("entró")
if op in ("+", "-"):
    print("no entra")
else:
    print("else correcto")

La salida es entró y else correcto. Ya fue el tema del artículo sobre operadores lógicos: la parte "-" sola es un texto no vacío, que Python trata como verdadero, y el or deja pasar toda la condición. El in corrige el error gratis, porque solo tiene un lado que escribir.

El diccionario: clave y valor

A la izquierda, el diccionario aluno con las claves nome, idade y nota unidas por flechas a los valores Ana, 20 y 8.5, con la frase que dice que se busca por la clave y nunca por la posición. A la derecha, cuatro formas de leer: aluno de idade da 20, aluno de peso da KeyError, aluno punto get de peso da None y aluno punto get de peso y 0 da 0. Una franja dice que los corchetes avisan con un error y que get responde con un valor por defecto.
Un diccionario une claves a valores y tiene tres formas de consulta: corchetes, get e in.

Un diccionario es un conjunto de pares. Cada par une una clave a un valor, y la consulta por la clave devuelve el valor. Se escribe entre llaves, con los dos puntos entre cada clave y el valor, y comas entre los pares:

nomes = {1: "enero", 2: "febrero", 3: "marzo", 12: "diciembre"}
for m in (1, 3, 12, 7):
    print(m, nomes.get(m, "mes inválido"))
print(nomes[3])
print(3 in nomes, 7 in nomes)
print(len(nomes))

La salida es 1 enero, 3 marzo, 12 diciembre, 7 mes inválido, después marzo, después True False y 4. Hay cuatro formas de usar el diccionario en ese trozo:

  • nomes[3] busca la clave 3 y devuelve "marzo".
  • nomes.get(7, "mes inválido") busca la clave 7, y como no existe devuelve el segundo argumento, el valor por defecto.
  • 3 in nomes pregunta si la clave existe, sin buscar el valor. El in de un diccionario mira las claves, nunca los valores.
  • len(nomes) cuenta los pares.

Los corchetes tienen un riesgo que el get no tiene. Si la clave no existe, el programa se detiene:

nomes = {1: "janeiro", 2: "fevereiro"}
print(nomes[7])
Traceback (most recent call last):
  File "main.py", line 2, in <module>
KeyError: 7

KeyError quiere decir "clave no encontrada", y el número que viene después de los dos puntos es la clave que faltó. La regla práctica: usa get con un valor por defecto cuando la ausencia de la clave sea un caso normal (un código desconocido, un mes mal escrito), y corchetes cuando la ausencia sea un defecto del programa que merece detenerse con un error claro.

Un diccionario también cambia. Se puede agregar un par, cambiar el valor de una clave y borrar un par:

pinos = {"led": 2, "botao": 4, "buzzer": 5}
print(pinos["led"])
pinos["rele"] = 15
print(pinos["rele"], len(pinos))
pinos["led"] = 13
print(pinos["led"])
del pinos["buzzer"]
print("buzzer" in pinos)
print({}, len({}))

La salida es 2, 15 4, 13, False y {} 0. La asignación pinos["rele"] = 15 crea el par cuando la clave es nueva y cambia el valor cuando la clave ya existe. Un diccionario vacío se escribe {}.

FastMindAsistente de IA local

La IA que recuerda tu vida y vive en tu ordenador

  • Memoria a largo plazo con la fuente de cada respuesta
  • Funciona sin conexión: tus datos se quedan en tu PC
  • Lee PDF, diapositivas, fotos y grabaciones de reuniones
Descargar gratis para Windows

Plan Esencial gratis para siempre

Cambiando la escalera por el diccionario

Dos cuadros de código lado a lado que traducen códigos de respuesta a mensajes. A la izquierda, una cadena de if, elif y else con cuatro comparaciones en diez líneas. A la derecha, un diccionario mensagens con cuatro pares y una única línea con get y valor por defecto. Una franja informa que las salidas son iguales en el simulador: 200 ok, 404 no encontrado y 503 desconocido.
La misma traducción de código a mensaje, escrita como cadena de elif y como diccionario.

Los códigos de respuesta de un servidor web, como el 404 de "página no encontrada", son el ejemplo perfecto: cada número tiene un mensaje. Aquí están las dos versiones, una después de la otra, con la misma entrada:

for c in (200, 301, 404, 500, 503, 418):
    if c == 200:
        msg = "ok"
    elif c == 301:
        msg = "movido"
    elif c == 404:
        msg = "no encontrado"
    elif c == 500:
        msg = "error del servidor"
    else:
        msg = "desconocido"
    print(c, msg)
for c in (200, 404, 503):
    print(c, {200: "ok", 404: "no encontrado"}.get(c, "desconocido"))

La primera parte imprime 200 ok, 301 movido, 404 no encontrado, 500 error del servidor, 503 desconocido y 418 desconocido. La segunda, con un diccionario menor para caber en una línea, imprime 200 ok, 404 no encontrado y 503 desconocido. El resultado es el mismo para los códigos que los dos conocen.

En la práctica, el diccionario va a una variable con nombre, como mensagens, y la lógica queda en una sola línea: msg = mensagens.get(c, "desconocido"). Agregar un código nuevo es agregar un par. Quien lee el programa ve los datos en un bloque y la regla en una línea, y quien quiere corregir un mensaje edita una línea de datos, sin preocuparse por los : y la sangría de los bloques. Esa es la ganancia principal. El diccionario no hace el programa más rápido de una manera que alguien note en una ESP32 con pocas opciones, y ni hace falta: la ventaja es de organización.

Las claves deben ser del mismo tipo

En un diccionario con la clave 404 (un int), la búsqueda por "404" (un texto) no encuentra nada, porque los dos valores son distintos:

mensagens = {200: "ok", 404: "no encontrado"}
print(mensagens.get("404", "desconocido"))
print(mensagens.get(int("404"), "desconocido"))
print(404 == "404")

La salida es desconocido, no encontrado y False. Es la trampa del input() otra vez: lo que el usuario escribe es siempre texto, y la clave 404 solo aparece cuando el programa convierte. El artículo sobre conversión de tipos tiene la causa, y la defensa aquí es la misma: convierte con int() antes de buscar, o escribe las claves del diccionario como texto desde el comienzo.

Mayúsculas y espacios

Cuando la clave es un texto escrito, "SEG" y "seg" son claves distintas. La defensa es normalizar lo que el usuario escribió antes de consultar:

dias = {"seg": "lunes", "ter": "martes"}
for entrada in ("seg", " SEG ", "Seg", "xyz"):
    c = entrada.strip().lower()
    print(repr(entrada), repr(c), dias.get(c, "?"))

La salida muestra que 'seg', ' SEG ' y 'Seg' se vuelven la clave 'seg' y dan como resultado lunes, y el 'xyz' queda en ?. El strip() quita los espacios de los extremos, el lower() pasa todo a minúsculas, y la función repr() imprime el texto con comillas para que los espacios queden visibles.

El orden de un diccionario

Ningún ejemplo hasta aquí depende del orden de los pares, y eso no es casualidad. Recorrer un diccionario con for entrega las claves en un orden que el programa no elige:

d = {"x": 1, "y": 2}
print(list(d))
print(sorted(d))
for chave in sorted(d):
    print(chave, d[chave])

En el simulador, la salida es ['y', 'x'] para list(d), después ['x', 'y'] para sorted(d) y luego x 1 y y 2. El Python del computador mostraría ['x', 'y'] ya en la primera línea, porque CPython guarda los pares en el orden en que entraron. MicroPython no promete ese orden, así que un programa que necesita una secuencia previsible debe pedirla con sorted(), que devuelve las claves en orden alfabético o numérico.

Un aviso más: si la misma clave aparece dos veces a la hora de escribir el diccionario, Python no se queja. En {"a": 1, "a": 2, "b": 3}, la a vale 2, y el diccionario tiene dos pares. Al copiar y pegar una línea, conviene comprobar si se cambió la clave.

Por qué MicroPython no tiene match/case

Python 3.10, de 2021, ganó una estructura nueva para elegir entre muchos valores, el match con varios case. Quien busca "switch en Python" encuentra ejemplos así:

codigo = 404
match codigo:
    case 200:
        print("ok")
    case 404:
        print("no encontrado")
    case _:
        print("otro")

En el Python del computador, la salida es no encontrado. En el simulador y en la ESP32 el resultado es otro:

Traceback (most recent call last):
  File "main.py", line 2
SyntaxError: invalid syntax

MicroPython implementa Python 3.4 y una selección de recursos de versiones más nuevas, y el match todavía no está entre ellos. La página de diferencias con Python 3.10, en la documentación oficial de MicroPython, lista la PEP 634, que define el match, como asunto en discusión. El programa, entonces, ni empieza: el error aparece en la compilación, antes de que corra la primera línea. Esto atrapa a los alumnos que copian un tutorial reciente, y vale la pena reconocer el síntoma: un SyntaxError justo en la línea del match.

Los reemplazos son los que presentó este artículo. Para valores exactos, el diccionario (o la cadena de elif con ==); para varios valores con la misma respuesta, el in; para rangos, el elif. El case _ del match, que atrapa lo que sobró, es el else de la cadena o el valor por defecto del get. Ninguno de estos cambios pierde nada en relación con el match para los usos de un programa de placa.

Qué cambia en la ESP32

El diccionario funciona igual en MicroPython, y dos usos aparecen mucho en proyectos de hardware. El primero es dar nombre a los pines, lo que facilita la lectura del programa:

from machine import Pin
pinos = {"led": 2, "rele": 15}
for nome in ("led", "rele", "motor"):
    if nome in pinos:
        saida = Pin(pinos[nome], Pin.OUT)
        saida.value(1)
        print(nome, "conectado al pin", pinos[nome], saida.value())
    else:
        print(nome, "no existe")

La salida es led conectado al pin 2 1, rele conectado al pin 15 1 y motor no existe. El in antes de la búsqueda evita el KeyError para un nombre que nadie registró, y el else dice cuál fue el nombre desconocido. El segundo uso es la traducción de códigos de error de un servidor web, como el ejemplo de los códigos de respuesta, cuando la placa habla con internet, tema del artículo sobre Wi-Fi, HTTP y MQTT.

Cada par de un diccionario ocupa memoria en la ESP32, que tiene bastante menos RAM que un computador. Para decenas de pares, como los meses, los días de la semana o los pines de un proyecto, eso no es problema. Una tabla con miles de elementos es otra conversación, y ese tipo de cálculo suele aparecer solo en proyectos mayores.

En el aula: dónde suele tropezar la elección

Los casos de abajo se repiten en los grupos que llegan a este tema. Cada uno produce un programa que corre y se equivoca, o se equivoca de una manera que no es la que el alumno busca.

El peldaño repetido

Al copiar y pegar un bloque del elif, el alumno olvida cambiar el valor, y el programa gana dos peldaños iguales. Python no se queja, y el segundo nunca se ejecuta:

op = "+"
if op == "+":
    print("suma")
elif op == "-":
    print("resta")
elif op == "+":
    print("suma otra vez, nunca corre")

La salida es solo suma. Cada peldaño se prueba en orden, y el primero que acierta decide, como en el artículo anterior. El defecto aparece cuando el alumno cree que el - no funciona y mira el bloque equivocado. En el diccionario, la versión de ese error es la clave repetida, que también pasa sin aviso.

El caso olvidado

El % de la calculadora, que cayó en el else. Una lista en el papel con todos los valores que el programa necesita tratar, antes de teclear, resuelve buena parte de estos olvidos.

El KeyError en la primera prueba

El alumno aprende el diccionario, usa corchetes por todos lados y escribe en la prueba una clave que no existe. El programa se detiene con KeyError, y el susto es mayor que el defecto. La pregunta que ayuda es: "¿la falta de esa clave es un caso normal?". Si lo es, get con valor por defecto o in.

Paréntesis en lugar de corchetes

Quien viene del print(...) y de otras funciones escribe meses(1) en lugar de meses[1]. Python entiende el meses(1) como una llamada, y como un diccionario no es una función:

Traceback (most recent call last):
  File "main.py", line 2, in <module>
TypeError: 'dict' object isn't callable

El mensaje del simulador dice isn't callable, y el del Python del computador dice is not callable, con el mismo sentido. "Callable" es lo que se puede llamar con paréntesis, y un diccionario se consulta con corchetes.

El diccionario para rangos

Al alumno le gusta tanto el diccionario que intenta usarlo para la nota, lista los valores de 0 a 10 y descubre que la 7.5 no está ahí. El razonamiento correcto es la pregunta de la figura del comienzo del artículo: rango pide elif.

El tipo de la clave

El input() devuelve texto, la clave es número, y la búsqueda nunca encuentra nada, sin error y sin aviso, como en el ejemplo del 404. Lo mismo vale para la mayúscula contra la minúscula y para el espacio perdido al final del texto escrito.

Ejercicio: el kiosco

Un menú tiene cuatro artículos, cada uno con un nombre y un precio: 1, pan con queso a 6; 2, jugo a 5; 3, café a 4; 4, bizcocho a 8. El programa recibe una lista de pedidos, dice qué es cada uno, acumula el total y avisa cuando el código no existe. Cada valor del diccionario es una tupla con dos elementos, y la línea nome, preco = cardapio[codigo] separa los dos de una vez.

cardapio = {1: ("pan con queso", 6), 2: ("jugo", 5), 3: ("café", 4), 4: ("bizcocho", 8)}
total = 0
for codigo in (2, 1, 4, 9, 3):
    if codigo in cardapio:
        nome, preco = cardapio[codigo]
        total = total + preco
        print(codigo, nome, preco)
    else:
        print(codigo, "artículo inexistente")
print("total", total)

La salida es 2 jugo 5, 1 pan con queso 6, 4 bizcocho 8, 9 artículo inexistente, 3 café 4 y total 23. El código 9 no existe, y el programa sigue adelante sin sumar nada y sin detenerse. Para pedir un único código al usuario, cambia el for por una lectura:

cardapio = {1: ("pan con queso", 6), 2: ("jugo", 5), 3: ("café", 4), 4: ("bizcocho", 8)}
codigo = int(input("Código del artículo (1 a 4): "))
if codigo in cardapio:
    nome, preco = cardapio[codigo]
    print(nome, "cuesta", preco)
else:
    print("artículo inexistente")
Código del artículo (1 a 4): 3
café cuesta 4

Con la entrada 9, la salida es artículo inexistente. Como el int() convierte antes de la búsqueda, la clave tiene el tipo correcto. Si el usuario escribe una palabra, el int() falla antes de que el diccionario entre en escena, y ese es un problema de conversión, tratado en el artículo sobre conversión de tipos.

Preguntas frecuentes

¿Cuál es más rápido, elif o diccionario?

Con pocas opciones, la diferencia no aparece en una ESP32. La elección vale por la claridad: si la condición es un rango, elif; si es un valor exacto en un grupo grande, diccionario. La cadena de elif compara un peldaño por vez, y el diccionario va directo a la clave, lo que solo haría diferencia con cientos de elementos.

¿Puedo usar texto como clave?

Puedes, y es común: {"led": 2}. Números, textos y tuplas sirven de clave. Una lista no sirve, porque puede cambiar.

¿Qué devuelve el get si no paso el segundo argumento?

Devuelve None, el valor que representa "nada". Es práctico para probar después, con if resultado is None. Si el programa va a imprimir el resultado, pasa un texto como valor por defecto.

¿Puedo poner un diccionario dentro de otro?

Puedes. Cada valor puede ser cualquier cosa, incluso otro diccionario, una lista o una tupla, como en el menú. El artículo sobre diccionarios de la serie muestra los usos con calma.

¿Cómo hago el equivalente del switch de C?

Con la cadena de elif para pocos casos o con un diccionario para muchos. Los valores de un diccionario también pueden ser funciones, lo que da un menú de comandos sin ningún elif. Ese uso viene con el módulo de funciones.

Para continuar

La ruta Lógica con Python tiene desafíos con muchas opciones. La Calculadora con menú pide la escalera de operaciones, el Nivel de acceso combina valores y reglas, los Meses del año piden la traducción de número en nombre, y el Código de error es el caso de los mensajes. Si el elif todavía confunde, relee el artículo sobre if, elif y else.

Con esto el módulo de decisión está completo: el programa ya elige qué hacer. El próximo tema es hacer que el mismo trozo de código corra muchas veces, los bucles, que empiezan por el motivo de repetir.

Para practicar en PyPlaca

Rutas

Lecciones

Fichas de componentes

FastMindAsistente de IA local

La IA que recuerda tu vida y vive en tu ordenador

  • Memoria a largo plazo con la fuente de cada respuesta
  • Funciona sin conexión: tus datos se quedan en tu PC
  • Lee PDF, diapositivas, fotos y grabaciones de reuniones
Descargar gratis para Windows

Plan Esencial gratis para siempre

FastTeacherPara profesores

Del programa al semestre planificado

  • Plan de enseñanza y calendario montados solos
  • Diario de clases listo para pegar en el SIGA
  • Diapositivas y listas de ejercicios con solucionario
Conocer FastTeacher

Versión Community gratis · Windows

FastMindAsistente de IA local

La IA que recuerda tu vida y vive en tu ordenador

  • Memoria a largo plazo con la fuente de cada respuesta
  • Funciona sin conexión: tus datos se quedan en tu PC
  • Lee PDF, diapositivas, fotos y grabaciones de reuniones
Descargar gratis para Windows

Plan Esencial gratis para siempre