Cómo Hacer Switch en Python

Cómo hacer switch en Python

Recuerdo anonadado cuándo descubrí que una sentencia tan cómoda y potente como el switch – case no existía en Python. ¿Cómo era posible? ¿Cómo sobrevivir sin él? Seguro que tú has pensado lo mismo en algún momento. Si estás aquí es porque, probablemente, ahora mismo estés dándole vueltas a la manera de hacerlo. Te explico en este artículo por qué no existe el switch en Python, las alternativas más comunes para realizarlo y mi propia solución, que seguro que te resultara muy útil.

La estructura de control switch – case no existe en Python. Una forma directa de simularlo es encadenando diversas sentencias if elif para todos los casos necesarios. Otra alternativa común es el uso de diccionarios, donde a cada caso se asocia una función con su código correspondiente.

Te muestro a continuación cada una de estas alternativas y mi propia solución, que resuelve algunos de los problemas de las alternativas más comunes. Pero antes te cuento por qué no existe switch en Python.

¿Por qué no existe switch en Python?

En Python no existe la estructura de control switch porque sus desarrolladores no se pusieron de acuerdo y no llegaron a una solución satisfactoria para su implementación. Entre los motivos principales destaco, por un lado, la sintaxis, pues no les convencía ninguna propuesta, y por otro, en mi opinión, la poca voluntad o disposición general para hacerlo.

Puedes leer (en inglés) todos los detalles en el documento oficial de propuesta de mejora 275 de Python y en documento oficial de propuesta de mejora 3103 de Python, ambos rechazados.

En cualquier caso, ahora tenemos que vivir con esta carencia, que si bien no es fatal, a veces complica un poco las cosas. Pero nada que no se pueda arreglar, como veremos a continuación.

Switch con sentencias if

Lo primero que solemos pensar como alternativa al switch es en utilizar diversas condicionales if. También suele pasar que mucha gente que está iniciándose en la programación, con independencia del lenguaje, suele desconocer la existencia de estructuras switch y tender de manera natural a esta solución con condicionales.

Se trata de escribir en secuencia distintas condicionales, donde cada una de esas condicionales gestiona un caso diferente. Vamos a verlo con un ejemplo.

Supongamos que necesitas un programa en Python que te muestre el día de la semana a partir de un número del 1 al 7, es decir, que muestre «lunes» si el día es 1, «martes» si el día es 2, y así sucesivamente. Si el día no está entre 1 y 7 mostraremos un error:

dia = 4 

if dia == 1:
  print('lunes')
if dia == 2:
  print('martes')
if dia == 3:
  print('miércoles')
if dia == 4:
  print('jueves')
if dia == 5:
  print('viernes')
if dia == 6:
  print('sábado')
if dia == 7:
  print('domingo')
if dia < 1 or dia > 7:
  print('error')

Veamos las principales ventajas e inconvenientes de esta aproximación. ventajas:

  • El código es fácil de entender.
  • Podemos tener ramas no excluyentes si nos interesara, pues las condicionales son independientes entre sí y poder de esta manera ejecutar varias ramas.
  • Podemos usar cualquier tipo de condición y no solo una comparación de igualdad.

A pesar de sus ventajas, esta manera de realizar la condicional múltiple tiene varios problemas:

  • Es muy repetitiva, porque obliga a repetir la comparación dia == una y otra vez.
  • Hace falta escribir una condición para el caso por defecto (que en el ejemplo es el caso de error), porque no podemos usar una rama else ya que tenemos varios if.
  • Es ineficiente ya que es necesario evaluar todas las condiciones aunque ya se haya ejecutado la rama correspondiente. Por ejemplo, si el día es 1 el primer if escribe «lunes» por pantalla. Acto seguido se siguen evaluando todas las demás condiciones aunque ya no tenga sentido hacerlo.

Este dos últimos problemas se podrían resolver anidando cada nuevo if dentro de una rama else, pero el código ya no sería tan fácil de entender además de resultar poco estético. Y tampoco podríamos tener ramas no excluyentes. Incluso se haría inmanejable si tenemos muchos casos. Fíjate en este ejemplo y entenderás enseguida por qué no es una opción aceptable:

dia = 4

if dia == 1:
	print('lunes')
else:
	if dia == 2:
		print('martes')
	else:
		if dia == 3:
			print('miércoles')
		else:
			if dia == 4:
				print('jueves')
			else:
				if dia == 5:
					print('viernes')
				else:
					if dia == 6:
						print('sábado')
					else:
						if dia == 7:
							print('domingo')
						else:
							print('error')

Para resolver este problema con el sangrado y el resultado tan poco estético, Python propone el uso de la sentencia elif que permite realizar una nueva rama excluyente indicando, además, una nueva condición.

Switch con sentencias if elif

Tal vez esta sea la solución preferida por muchos. Ten en cuenta que esta solución implica que todas las ramas son excluyentes entre sí, es decir, solo se puede ejecutar una. Veamos como transformar el ejemplo anterior antes de ver sus ventajas e inconvenientes. Para lograrlo puedes hacer lo siguiente encadenando sentencias if elif:

dia = 4

if dia == 1:
	print('lunes')
elif dia == 2:
	print('martes')
elif dia == 3:
	print('miércoles')
elif dia == 4:
	print('jueves')
elif dia == 5:
	print('viernes')
elif dia == 6:
	print('sábado')
elif dia == 7:
	print('domingo')
else:
	print('error')

Cómo puedes ver el código resulta mucho más claro ahora. Las ventajas de esta solución son:

  • Fácil de entender pues el código es muy claro.
  • No es necesario escribir una condición para la rama por defecto. Al ser todas las ramas excluyentes únicamente necesitamos una rama else final.
  • Es más eficiente que el caso anterior, pues en cuanto se encuentra una condición que se cumpla, el resto ya no se evalúa. En el peor de los casos, que es llegar a la rama else, se evalúan igualmente todas las condiciones.
  • Se puede usar cualquier tipo de comparación en las condiciones.

Y sus inconvenientes:

  • Código repetitivo. Igualmente necesitamos escribir todas las comparaciones aunque sean prácticamente iguales (solo cambia el valor final).
  • Ramas excluyentes entre sí.

Switch con diccionarios

Otra solución interesante radica en el uso de diccionarios. La idea principal es crear un diccionario donde las claves son los posibles valores que puede tomar la variable sobre la que queremos hacer el switch. Asociado a cada clave irá una función con el código correspondiente a cada caso. Esto guarda relación y es perfectamente compatible con el patrón de diseño command en Python.

De esta manera, y siguiendo con el ejemplo anterior, creamos una función para cada caso que se encarga de imprimir por pantalla el día de la semana y otra función para mostrar el error. Después creamos el diccionario asociando la función lunes al valor 1, la función martes al valor 2, etc.

A la hora de hacer el switch solo tienes que tomar del diccionario la función correspondiente a partir de la clave (la variable del switch) e invocarla. Fíjate en que es mejor no usar la notación de corchetes y en su lugar utilizar la función get del diccionario. Esta función, además de la clave, acepta un parámetro que es el valor a devolver si la clave no existe en el diccionario, lo que se llama valor de fallback. Esta es una manera muy cómoda de definir un caso por defecto, además de impedir que se rompa la ejecución de nuestro programa si una clave no existe. Te muestro el código:

def lunes():
	print('lunes')

def martes():
	print('martes')

def miercoles():
	print('miércoles')

def jueves():
	print('jueves')

def viernes():
	print('viernes')

def sabado():
	print('sábado')

def domingo():
	print('domingo')

def error():
	print('error')

switch_semana = {
	1: lunes,
	2: martes,
	3: miercoles,
	4: jueves,
	5: viernes,
	6: sabado,
	7: domingo
}

dia = 8

#tomamos la función asociada a la variable y la invocamos
switch_semana.get(dia, error)()

Ventajas de la solución con diccionarios:

  • Código legible y sencillo.
  • No es necesario repetir la comparación.
  • Es muy eficiente, pues el acceso al diccionario a través de su valor de hash resulta muy rápido.
  • Permite asociar la misma función a distintos valores de clave sin necesidad de repetir código.
  • Permite añadir o eliminar dinámicamente en tiempo de ejecución casos si fuera necesario con solo añadir o eliminar elementos al diccionario.

Inconvenientes:

  • Es necesario definir las funciones o, si son sencillas, expresiones lambda como valores del diccionario. Esto implica escribir más código del deseable.
  • Solo funciona para comparaciones de igualdad pues es la manera en la que trabajan los diccionarios.
  • No permite la ejecución de varias ramas sin modificar bastante el código propuesto.

Este ejemplo es muy sencillo y se ha hecho así por motivos didácticos para ilustrar la solución. En un caso real, en el que el código de cada rama es prácticamente el mismo, bastaría con crear el diccionario asociando a cada número el nombre del día y mostrando directamente por pantalla el valor obtenido, resultando en una solución mucho más compacta.

Mi solución: clase switch y la sentencia with

Te regalo aquí mi solución particular que, como puedes ver, es un poco diferente de las propuestas estándar que puedes encontrarte comúnmente por la red. Cuando diseñé esta solución la idea principal era que la sintaxis fuera lo más parecida al switch tradicional y, además, resolver algunos de los problemas de las alternativas de implementación más comunes.

Para esto he desarrollado una clase llamada switch que se encarga de la gestión de la condicional múltiple. Además, he querido tener en cuenta el comportamiento del switch en lenguajes como C o Java, donde es necesario incluir un break en el código de las ramas para evitar que se ejecute el código de las siguientes. Esto es así porque esa característica es útil en muchos casos, como veremos más tarde.

Comparaciones de igualdad

Antes de ver cómo está implementada dicha clase te muestro la manera de utilizarla, en concreto, para el mismo ejemplo que estamos tratando, en donde todas las comparaciones son de igualdad:

dia = 4

with switch(dia) as s:
	if s.case(1):
		print('lunes')
	if s.case(2):
		print('martes')
	if s.case(3):
		print('miércoles')
	if s.case(4):
		print('jueves')
	if s.case(5):
		print('viernes')
	if s.case(6):
		print('sábado')
	if s.case(7):
		print('domingo')
	if s.default():
		print('error')

Si te fijas, la sintaxis trata de emular la estructura clásica del switch. Si ejecutamos el código anterior obtenemos lo siguiente por pantalla:

jueves
viernes
sábado
domingo

Este comportamiento no es el deseado, pues queremos que no se ejecute más código una vez que se ejecuta la rama del 4. Para eso añadiremos un break. La manera de hacerlo es incluir un segundo parámetro booleano en las funciones case en las que necesitemos ese break con el que indicaremos mediante True que queremos que no se ejecuten las ramas siguientes. En este caso son todas las ramas las que deben llevar break, de forma que el código que logra el comportamiento que queremos es el siguiente:

dia = 4

with switch(dia) as s:
	if s.case(1, True):
		print('lunes')
	if s.case(2, True):
		print('martes')
	if s.case(3, True):
		print('miércoles')
	if s.case(4, True):
		print('jueves')
	if s.case(5, True):
		print('viernes')
	if s.case(6, True):
		print('sábado')
	if s.case(7, True):
		print('domingo')
	if s.default():
		print('error')

Vamos a modificar un poco el ejemplo para que solo se nos muestre por pantalla si es día de semana o fin de semana. Para esto es interesante agrupar varios casos y que se ejecute el código de las siguientes ramas hasta encontrar un break. En las ramas en las que no queremos ejecutar nada de momento ubicaremos la sentencia pass que, como sabes, no ejecuta nada en Python. Fíjate que solo ponemos break donde nos interesa parar la ejecución del código, es decir, en los casos 5 y 7:

dia = 4

with switch(dia) as s:
	if s.case(1): pass
	if s.case(2): pass
	if s.case(3): pass
	if s.case(4): pass
	if s.case(5, True):
		print('día de semana')
	if s.case(6): pass
	if s.case(7, True):
		print('fin de semana')
	if s.default():
		print('error')

La solución que aquí te propongo tiene la ventaja de poder aplicar cualquier tipo de comparación y no solo comparaciones de igualdad, te lo muestro a continuación.

Otras comparaciones

Si necesitamos realizar cualquier otra comparación entre la variable del switch y el parámetro proporcionado en cada uno de los casos, podemos hacerlo sin problema, siempre y cuando le proporcionemos al switch la forma en la que hacer esa comparación. Esto se puede lleva a cabo indicándole al switch un segundo parámetro llamado comparator que recibe una función que realiza la comparación de dos argumentos. También se puede usar una función lambda como hago en el ejemplo que hay a continuación.

Vamos a suponer que lo que necesitas ahora es comprobar si el valor de tu variable está dentro de determinadas listas o tuplas. Proporcionamos la función de comparación (en la que utilizamos el operador in que comprueba si un valor está dentro de una colección) de la siguiente manera. En los casos basta con indicar las listas con las que queremos realizar la comparación:

with switch(dia, comparator=lambda x, y: x in y) as s:
	if s.case((1, 2, 3, 4, 5), True):
		print('día de semana')
	if s.case((6, 7), True):
		print('fin de semana')
	if s.default():
		print('error')

Otra manera de realizar este mismo ejemplo es comprobar si la variable está dentro de un rango, que se puede hacer de la siguiente manera:

dia = 4

with switch(dia, comparator=lambda x, y: y[0] <= x <= y[1]) as s:
	if s.case((1, 5), True):
		print('día de semana')
	if s.case((6, 7), True):
		print('fin de semana')
	if s.default():
		print('error')

De esta manera se pueden lograr condicionales múltiples muy potentes.

Exclusividad en los casos

Te habrás dado cuenta que puede suceder que una variable cumpla varios casos. De ser así, se ejecutarían todos y cada uno de esos casos. Lo vemos con un ejemplo.

Imagina que necesitamos mostrar por pantalla si un número es múltiplo de otro número primo de un solo dígito, es decir, de 2, 3, 5 o 7. Para ello hacemos un switch proporcionando un comparador que me dice si es múltiplo de cada uno de esos números primos. Puede suceder que haya algún número que sea múltiplo de varios de ellos, por ejemplo, el 15, que lo es de 3 y de 5, por tanto, se ejecutarán dos ramas:

numero = 15

with switch(numero, comparator=lambda x, y: not x % y) as s:
	if s.case(2, True):
		print(f'{numero} es múltiplo de 2')
	if s.case(3, True):
		print(f'{numero} es múltiplo de 3')
	if s.case(5, True):
		print(f'{numero} es múltiplo de 5')
	if s.case(7, True):
		print(f'{numero} es múltiplo de 7')
	if s.default():
		print(f'{numero} no es múltiplo de 2, 3, 5 o 7')

Al ejecutar este código obtenemos lo siguiente por pantalla, que es justo el comportamiento que esperábamos:

15 es múltiplo de 3
15 es múltiplo de 5

Pero también pueden existir situaciones en las que no deseamos que se ejecuten todas las ramas en las que las condiciones sean ciertas, si no que se ejecute solo la primera de ellas que sea cierta. Es lo que he llamado modo estricto. En dicho modo, solo se ejecuta el código de la rama correspondiente (y de las siguientes hasta encontrar un break).

Para activar el modo estricto basta con indicarle el parámetro strict=True al switch. Así, para mostrar solo el primero de esos cuatro números primos de los que el número en cuestión sea múltiplo haremos lo siguiente:

numero = 15

with switch(numero, strict=True, comparator=lambda x, y: not x % y) as s:
	if s.case(2, True):
		print(f'{numero} es múltiplo de 2')
	if s.case(3, True):
		print(f'{numero} es múltiplo de 3')
	if s.case(5, True):
		print(f'{numero} es múltiplo de 5')
	if s.case(7, True):
		print(f'{numero} es múltiplo de 7')
	if s.default():
		print(f'{numero} no es múltiplo de 2, 3, 5 o 7')

Ahora ya estamos en disposición de ver el código de la clase switch, que te invito a estudiar. Basta con que copies y pegues esa clase en tu programa para poder usarla. También puedes ubicarlo en un fichero adicional y hacer un import si así lo deseas. El código es el siguiente:

class switch:

	def __init__(self, variable, comparator=None, strict=False):
		self.variable = variable
		self.matched = False
		self.matching = False
		if comparator:
			self.comparator = comparator
		else:
			self.comparator = lambda x, y: x == y
		self.strict = strict

	def __enter__(self):
		return self

	def __exit__(self, exc_type, exc_val, exc_tb):
		pass

	def case(self, expr, break_=False):
		if self.strict:
			if self.matched:
				return False
		if self.matching or self.comparator(self.variable, expr):
			if not break_:
				self.matching = True
			else:
				self.matched = True
				self.matching = False
			return True
		else:
			return False

	def default(self):
		return not self.matched and not self.matching

Siéntete libre de modificar este código para adaptar su comportamiento a tu gusto. Por ejemplo, si deseas que por defecto todas las ramas incluyan un break, cambia el valor por defecto del parámetro break_ en la función case a True. De manera similar puedes hacer lo mismo con el parámetro strict en la función de inicialización __init__.

Ventajas y desventajas

Veamos ahora las ventas e inconvenientes de la solución propuesta. Ventajas:

  • Sintaxis similar al switch clásico de otros lenguajes de programación.
  • Código claro y fácil de leer y escribir.
  • Permite la ejecución de las ramas adyacentes posteriores hasta encontrar un break, lo que hace que se puedan agrupar diversas condiciones para ejecutar un sólo fragmento de código..
  • Posibilita la utilización de cualquier tipo de comparación en las condiciones de manera sencilla y no solo comparaciones de igualdad.
  • Permite la ejecución de diversas ramas si se cumple su condición. No obstante, si no se desea este comportamiento, se puede activar el modo estricto que asegura la ejecución de una sola rama (o las siguientes hasta encontrar un break).

De todas formas esta solución tampoco está exenta de problemas (y lo primero es la autocrítica). Desventajas:

  • Es ineficiente. Sin duda es más lenta que la solución con diccionarios o simplemente con ifs encadenados. Esto también es debido a las comparaciones que hay que realizar internamente en la clase switch para asegurar el correcto funcionamiento. Es el pequeño precio que hay que pagar para poder contar con su potencia y funcionalidades.
  • El código es levemente repetitivo, pues es necesario escribir un if en cada condicional o un pass en las ramas que no tengan código.
  • Mientras que los diccionarios o las condicionales básicas son parte del lenguaje y se pueden utilizar sin más, para esta solución es necesaria la inclusión de la clase switch en tu programa para poder utilizarlo.

Saca tus propias conclusiones y si puedes vivir con esos pequeños problemas a cambio de las grandes ventajas que aporta esta solución, ¡adelante! Pero depende de ti y del problema que quieras resolver qué aproximación usar, pues no hay una solución universal.

Y esto es todo. Espero que te haya resultado útil este post. Si te gusta mi solución, siéntete libre de tomarla, copiarla, compartirla o adaptarla a tu gusto. Y si no te gusta, por supuesto puedes ignorarla o criticarla. Y mejorarla, claro. Pero lo importante es que te haya quedado clara la manera de poder realizar un switch en Python, con independencia de la solución que adoptes.

La Hoja de Referencia de Python – ¡Gratis!

La Hoja de Referencia de Python - Código Pitón
Consigue trucos, consejos y actualizaciones y, por supuesto, la Hoja de Referencia de Python gratis.



Antes de suscribirte consulta aquí la Información Básica sobre Protección de Datos. Responsable de los datos: Laura Otero Moreira. Finalidad de la recogida y tratamiento de los datos personales: enviarte boletín informativo de Python y comunicaciones comerciales. Legitimación: tu consentimiento. Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (MailRelay). Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control. Más información en nuestra política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal, incluida información sobre acceso, conservación, rectificación, eliminación, seguridad y otros temas.