ReturnS en Python
La palabra reservada “return” en la declaración de una función es simple, pero guarda algunos secretos, sin ir más lejos podemos encontrar funciones declaradas que tengan uno o incluso varios de estos tipos de return:
- Return que devuelve un valor.
- Return que devuelve varios valores.
- Return None.
- Return vacío (en inglés, “return void”).
- Sin Return.
Y posiblemente te resulten curiosos los detalles avanzados sobre “return” (quizás alguno lo tengas más que sabido por haberlo utilizado):
- “return”, en la declaración de una función, indica que “se ha terminado” de procesar lo que se estuviera haciendo dentro de la función.
- La declaración de una única función puede tener un “return”, varios “returns” o ninguno.
- Un “return” puede devolver un valor, varios valores o ninguno.
- “return”, en la declaración de una “función generadora” , indica que “se ha terminado” de producir elementos y que lance la Excepción StopIteration.
En este artículo se revelarán cada uno de estos secretos, lo que nos proporcionará varias herramientas para tener una perspectiva muy amplia, nos facilitará a la hora de programar y, por tanto, haremos más código funcional en menos tiempo.
Nota si estás aprendiendo acerca de las funciones de Python: Además de aprender sobre los diferentes tipos de Return en Python, es muy interesante aprender también sobre *args y **kwargs que explicamos pinchando aquí.
El código de este artículo lo puedes encontrar repositado en Github: https://github.com/Invarato/JarrobaPython/tree/master/returns
Retornar un valor
El «return» más simple que se puede declarar en una función es el que devuelve un único valor:
def dame_un_texto():
return "Texto"
def dame_un_numero():
return 123
def dame_un_booleano():
return true
def dame_un_listado():
return ["A", "B", "C"]
def dame_un_diccionario():
return {
"casa": "house",
"tarta": "cake"
}
Usar estos debería ser sencillo, por ejemplo podríamos utilizar un par de estos del siguiente modo:
valor = dame_un_texto()
print(valor)
print(dame_un_listado())
Muestra por pantalla:
Texto
['A', 'B', 'C']
También se puede devolver como único valor None; aunque este “return None” lo veremos más adelante, porque tiene unos usos especiales.
def dame_un_none():
return None
Retornar varios valores a la vez
En Python se pueden devolver “varios valores” a la vez, y lo pongo entre comillas porque realmente lo que se devuelve es una Tupla, y como veremos esta Tupla se puede usar de un modo peculiar en Python.
Es lo mismo devolver una Tupla que se declara con paréntesis con varios valores dentro separados por comas (del mismo o diferente tipo):
def dame_un_tupla():
return ("Texto", True, 123)
Que devolver estos valores sin los paréntesis en el “return” directamente (esto es lo que da la sensación de devolver “varios valores” a la vez, pero realmente se devuelve una Tupla).
def dame_varios_valores():
return "Texto", True, 123
Podemos probar a imprimir ambos:
print(dame_un_tupla())
print(dame_varios_valores())
Que nos va a mostrar exactamente lo mismo, dos Tuplas:
('Texto', True, 123)
('Texto', True, 123)
Formas de declarar una Tupla
Una Tupla se puede declarar normalmente entre paréntesis y cada valor separado de una coma; por ejemplo:
mi_tupla = ("Texto", True, 123)
Pero también sin paréntesis pero siempre que cada valor esté separado de una coma (las comas en una Tupla son obligatorias):
mi_tupla = "Texto", True, 123
Por lo que un valor que termine en una coma será interpretado como una Tupla de un único elemento (de ahí que sea la coma obligatoria, de no poner la coma sería interpretado como un único valor):
mi_tupla = "Texto",
# Nótese la coma después del único valor
Y aunque se ponga paréntesis a la Tupla de un elemento, también requiere la coma del final:
mi_tupla = ("Texto",)
# Nótese la coma después del valor y encerrada dentro de los paréntesis
Podremos poner “los valores” que necesitemos que sean de retorno en una variable que de tipo Tupla para devolverlos en el “return” del siguiente modo (cada variable de cada valor apuntará a una posición de la Tupla):
def dame_varios_valores_en_una_tupla():
# Suponemos que estas variables vienen de un proceso más complicado
primer_valor = "Texto"
segundo_valor = True
tercer_valor = 123
# Normalmente se pone junto al return, pero quiero que se vea claro que esta manera de declarar la tupla para luego utilizar estos valores fuera de la función
mi_tupla = primer_valor, segundo_valor, tercer_valor
return mi_tupla
Y va a imprimir lo mismo que antes:
print(dame_varios_valores_en_una_tupla())
Muestra:
('Texto', True, 123)
Esto es interesante porque si vienes de otros lenguajes (como Java) que el “return” solo devuelve un elemento aquí “también pero no”. En Python va a devolver la Tupla:
tupla_devuelta = dame_varios_valores_en_una_tupla()
Y podremos utilizar sus valores por su posición:
primer_valor = tupla_devuelta[0]
segundo_valor = tupla_devuelta[1]
tercer_valor = tupla_devuelta[2]
Pero es que Python nos facilita más la vida al extender estos valores (como cuando declarábamos la Tupla sin paréntesis, pero al revés):
primer_valor, segundo_valor, tercer_valor = tupla_devuelta
Y desde luego podremos hacer esto directamente en al llamar a la función y ahorrarnos mucho código:
primer_valor, segundo_valor, tercer_valor = dame_varios_valores_en_una_tupla()
Por lo que hemos devuelto “varios valores” en el “return” cuando solo hemos devuelto una Tupla y automáticamente se nos ha extendido en varias variables.
Return None vs Return vacío (Return void) vs Sin Return
Cabe destacar la diferencia entre “Return None vs Return vacío (Return void) vs Sin Return”.
El valor devuelto por la llamada a una función por cuyo código pase por alguno de estos tres “returns” (“Return None”, “Return void” o “Sin Return”) es siempre None:
valor_retornado = funcion_con_uno_de_estos_return()
print(valor_retornado)
Para las tres funciones imprime simplemente:
None
Return None
Indica que va a devolver un valor para su uso fuera de la función, en este caso es None (pero podría haber sido cualquier otro, como un número o un texto). Se suele utilizar para indicar que no hay un valor posible que devolver (por ejemplo, una función que consulte un dato en una base de datos, si existe el dato lo devolverá, sino existe ningún dato a devolver pues podremos devolver un None para indicar que no había nada).
En el siguiente ejemplo tenemos una función que traduce una palabra del español al inglés, si está la palabra en el diccionario la devolverá, sino lo está entonces devolverá None:
def traduce_a_ingles(palabra_espanol):
diccionario_espanol_ingles = {
"casa": "house",
"tarta": "cake"
}
try:
return diccionario_espanol_ingles[palabra_espanol]
except KeyError:
return None
Se puede probar con:
palabra_en_ingles = traduce_a_ingles("casa")
print("casa = {}".format(palabra_en_ingles))
palabra_en_ingles = traduce_a_ingles("habitación")
print("habitación = {}".format(palabra_en_ingles))
Mostrará por pantalla:
casa = house
habitación = None
Return vacío (En inglés “return void”; es un “return” sin valores de devolución)
Se utiliza para indicar que se ha terminado (de hacer lo que se haga dentro de la función) y que la función no va a tener ningún valor que devolver. Se emplea parecido a la palabra reservada “break” en un bucle “for”, para terminar anticipadamente la ejecución del código tras hacer “algo” (llamar a otra función, imprimir un texto con “print” , etc.) y no se quiere devolver nada.
El siguiente ejemplo buscará una palabra dentro de un listado con un bucle “for”, cuando la encuentre imprimirá con “print” que la ha encontrado y terminará la función (en este caso “return” tiene una doble función: sustituye a “break ” para terminar al bucle y a la función a la vez). El “return” en la última línea de la función lo pongo para indicar que se puede poner o no (mejor no ponerlo, nos ahorramos tener que escribirlo y además queda más limpio el código, ya que se supone que siempre hay un “return vacío” al final de la función).
def busca(cosa_a_buscar):
listado_cosas = ["boligrafo", "taza", "cuchara"]
for cosa in listado_cosas:
if cosa == cosa_a_buscar:
print("Encontrado")
return
print("No encontrado")
return # El return vacío al final de la declaración de la función se puede omitir
Se puede probar con:
busca("taza")
busca("armario")
Mostrará por pantalla:
Encontrado
No encontrado
¿Devuelve algo un «return void»?
Como se indicó al principio, este “return vacío” va a devolver None, por lo que no tiene sentido apuntar con una variable a la llamada a la función, aunque se podría hacer sin que haya errores ni de compilación ni de ejecución:
valor_retornado = busca("armario")
print(valor_retornado)
Pero siempre mostrará:
None
Esto es más una errata a la hora de programar y como no hace nada pues evita que de error en tiempo de ejecución, pero no es correcto el apuntar a algo que no va hacer nunca nada; además, puede inducir a pensar que sí que devuelve algo esa función, cuando nunca va a devolver más que un «None».
En Iteradores (más propiamente para los Generadores, que son Iteradores) este “return vacío” es el único que se permite utilizar (si se le añaden valores de retorno depende de la versión de Python te dará error o no servirá de nada el valor devuelto). El “return vacío” en un Iterador indica que ha terminado de devolver elementos(para los Generadores indica que ha terminado de producir elementos). Este “return vacío” como no tiene valor de retorno también indica que no tiene nada que devolver (pero es que un Iterador que ha terminado de iterar no va a tener nada más que devolver nunca, por lo que es lógico).
def funcion_generadora_de_numeros_en_texto():
yield "UNO"
yield "DOS"
yield "TRES"
return
iterable = funcion_generadora_de_numeros_en_texto()
Además, el “return vacío” lanza una Excepción StopIteration para señalizar que el Iterador se ha agotado (no hay que lanzar la Excepción StopIteration a mano).
Se puede ver al probar la “función generadora” con “next”:
try:
print(next(iterable))
print(next(iterable))
print(next(iterable))
print(next(iterable))
except StopIteration:
print("El Generador se ha agotado y ha devuelto StopIteration")
Muestra por pantalla:
UNO
DOS
TRES
El Generador se ha agotado y ha devuelto StopIteration
Pero ojo, aunque el “return vacío” de una “función generadora” devuelva una Excepción no va a poder ser capturado nunca por un try/catch. Con un ejemplo se ve mejor:
def funcion_generadora_de_numeros_en_texto():
yield "UNO"
yield "DOS"
try:
return
except:
print("Nunca se captura nada, ni siquiera el StopIteration que lanza el return")
StopIteration vs “return vacío” en Iteradores (Generadores que son Iteradores)
No es buena idea, ni siquiera una buena práctica, lanzar por nuestra cuenta un StopIteration, en las funciones generadoras (salvo casos muy precisos), ya que podríamos esconder otras Excpeciones y además se podría capturar sin querer donde no es necesario. Por ejemplo, dentro de la misma “función generadora”:
def funcion_generadora_de_numeros_en_texto():
yield "UNO"
yield "DOS"
try:
raise StopIteration
except:
print("Se imprime esto por la excepción")
Es importante saber que una “función generadora” devuelve un StopIteration, pero no el tener que lanzarla por nosotros mismos. Mejor utilizar el “return vacío” para indicar que se ha agotado nuestro Generador.
Con esto entendemos que la diferencia es:
- StopIteration: es una Excepción que se lanza cuando un Generador (Generator) no le queda nada más por producir. Es una Excepción, por lo que puede ser capturada por un try/except.
- Return vacío: Indica que la función ha terminado y que no tiene nada que devolver. Además, en un Iterador lanza una Excepción StopIteration para señalizar que este Iterador se ha agotado. NO es una Excepción, por tanto, no puede ser capturada por un try/except.
Sin Return
No poner return es el caso opcional de no poner el “return vacío”. Es decir que esto:
def funcion():
# Sin return
Es lo mismo que esto:
def funcion():
return
Salvo que queda más limpio no poner el “return” ya que se entiende que va a devolver el “return vacío”.
Y lo mismo que antes, va a devolver None, pero apuntar una variable a la llamada a la función está mal escrito ya que (al igual que el “return vacío”) no va a servir para nada y ensucia el código.
Varios returns en una misma función
Para ver el uso eral de varios “returns” en una misma función hay que conocer que:
- Cuando se pone un “return” en una función indica que “se ha terminado” de procesar lo que se estuviera haciendo dentro de la función
- Que el “return” no tiene por qué estar al final de la declaración de la función
- Siempre al final de la función va a existir al menos el “return vacío”, aunque no lo escribamos
Se suelen emplear varios “returns” cuando hay varios valores que devolver y la estructura de condicionales “ifs” es compleja.
Por ejemplo, una función que pinte el color que le pasemos de azul, por ejemplo, si le pasamos el “AMARILLO” lo pintará de “VERDE” y este será el valor que devuelva. Como vamos a poner varios colores que se mezclan con el azul, nos queda una condicional un poco compleja, por lo que resulta sencillo poner varios “returns”; y en caso de que nos entre en la función un valor que no tengamos, devolveremos un None:
def pinta_de_azul(color):
if color == "BLANCO":
return "AZUL"
elif color == "AMARILLO":
return "VERDE"
elif color == "ROJO":
return "MORADO"
else:
return None
print(pinta_de_azul("AMARILLO"))
print(pinta_de_azul("COLOR QUE NO EXISTE"))
Nos mostrará por pantalla:
VERDE
None
También se utiliza para terminar anticipadamente el código de la función, por ejemplo, dentro de un bucle (a modo de break, como vimos antes, salvo que aquí devolvemos un valor). Y al final de la función tendremos otro “return vacío” por defecto siempre o lo podremos declarar con un valor que nos convenga.
Por ejemplo, una función que nos devuelva si una persona está o no en una fiesta. Buscará en un bucle “for” persona a persona de un listado a ver si coincide con el que le hemos pasado en el parámetro de la función, si lo encuentra devuelve True en un return dentro del bucle, por lo que termina el bucle, termina la función y devuelve el valor; si el bucle termina sin devolver nada, devolveremos False en el return que termina a la función:
def esta_en_la_fiesta(persona_a_buscar):
listado_personas_en_la_fiesta = ["Paco", "María", "Juan"]
for persona in listado_personas_en_la_fiesta:
if persona == persona_a_buscar:
return True
return False
print(esta_en_la_fiesta("María"))
print(esta_en_la_fiesta("Rodrigo"))
Mostrará por pantalla:
True
False