Las funciones de Python a fondo
Empieza este capítulo con la base más fundamental sobre las funciones en Python, y ves progresando a lo largo de todo el capítulo.

Espacio publicitario
¿Qué es una función?
Una función es un bloque de código que se ejecuta al ser llamado. En tales llamadas, puede haber una serie de argumentos.
Además, las funciones pueden ser utilizadas para devolver valores dentro del programa, con el fin de procesarlos y utilizarlos en él.
Sintaxis de una función
def nombre_función(parámetros):
# bloque de código
expresión de retorno
Las funciones se declaran con la palabra reservada
def
, les damos un nombre, y entre sus
paréntesis, ponemos opcionalmente el número de parámetros
que queramos.
Después de los dos puntos, indentaremos (tabularemos) un
bloque con el código de la función, y finalmente, como
última instrucción, la expresión de retorno con la palabra
return
. Esta expresión, al igual que los
parámetros, también es opcional. Si no quieres devolver
valores, no la tienes que utilizar.
Crear una función
Vamos a crear una función muy simple:
def funcion():
print("¿Me has llamado?")
Llamar a una función
Para ejecutar el código de la función, realizaremos una llamada con su nombre y unos paréntesis:
funcion()
¿Me has llamado?
Función con bloque de código vacío
Mientras vas desarrollando la estructura completa del código, puede ser que necesites dejar declaradas las funciones, pero todavía sin el código correspondiente.
Como todo elemento con dos puntos en Python
(:
), debes tener un bloque obligatorio
indentado:
def funcion():
IndentationError: expected an indented block after function definition on line X
Error de indentación: se esperaba un bloque indentado después de la definición en la línea X
En estos casos, utiliza la palabra reservada
pass
, para dejar la función vacía:
def funcion():
pass
O bien, añade un comentario docstring:
def funcion():
"""Añadir código más adelante."""
Espacio publicitario
Parámetros en las funciones
En muchas ocasiones utilizaremos parámetros para poder pasar valores a las funciones. A estos valores se les denominan argumentos.
Veamos un ejemplo:
def suma(a, b):
print(a + b)
suma(10,56)
66
Que te quede clara la diferencia:
-
Parámetro es la variable que lleva la declaración de
la función.
En el ejemplo anterior,
a
yb
son parámetros. -
Argumento es el valor que se le pasa a la variable de
la función.
En el ejemplo anterior,
10
y56
son argumentos.
Ahora, si intentas utilizar el resultado de la suma anterior en tu programa, no lo conseguirás, ya que está diseñada solo para imprimir valores en la consola:
def suma(a, b):
print(a + b)
resultado = suma(10,56)
print(f"El resultado es: {resultado}.")
None
El resultado es un valor nulo que indica que la función no está devolviendo ningún valor.
Espacio publicitario
Devolver valores
Para devolver valores, utilizaremos la antes mencionada
palabra reservada return
.
La función del ejemplo anterior, puede ser modificada para que cumpla con este propósito:
def suma(a, b):
return a + b
resultado = suma(10,56)
print(f"El resultado es: {resultado}.")
El resultado es: 66.
Tipos de argumentos en las funciones de Python
Analicemos los tipos de argumentos que utiliza Python en las funciones. Conocerlos bien, es necesario para poder aprovecharlas como es debido.
- Argumentos posicionales
- Argumentos de clave
- Argumentos por defecto
- Argumentos arbitrarios posicionales
- Argumentos arbitrarios de clave
- Argumentos con expresión (estos no son un tipo realmente, se pueden aplicar a cualquiera de los otros)
Veamos ejemplos prácticos con cada uno de ellos.
Argumentos posicionales

Los argumentos posicionales son los más típicos a la hora de realizar llamadas.

Este tipo de argumentos, son los que se correlacionan de forma posicional con la declaración de parámetros. Aquí tienes un ejemplo:
def suma(a, b):
return a + b
resultado = suma(10,56)
En el ejemplo, 10
y 56
son
argumentos posicionales. Se pasan en orden de posición a
la función.
Por lo tanto, el valor 10
(valor 1) se le
pasa al parámetro a
(parámetro 1) y el valor
56
, al parámetro b
(parámetro
2).
A los argumentos posicionales se les denomina en inglés como positional arguments.
Espacio publicitario
Argumentos de clave

Los argumentos de clave son los que se especifican como pares de clave y valor.

En este caso, en la llamada se hace referencia explícitamente a que parámetro (clave), va a ir asociado un valor.
Aquí tienes un ejemplo:
def suma(a, b):
return a + b
resultado = suma(b=56, a=10)
print(resultado)
66
En el ejemplo, se puede comprobar como el orden de especificación de argumentos no importa.
No hace falta que cruces los argumentos como en la imagen, utiliza el orden que quieras.
A los argumentos de clave se les denomina en inglés como keyword arguments.
Espacio publicitario
Argumentos por defecto

Los argumentos por defecto son valores que se asignan a los parámetros de una función de manera predeterminada. Esto significa que si se llama a la función sin proporcionar un valor para el parámetro, se utilizará el valor predeterminado.

En este caso, los parámetros por defecto, irán después de los normales, en la declaración de la función.
Por ejemplo, no puedes hacer esto:
def suma(a=10, b):
SyntaxError: parameter without a default follows parameter with a default
Error de sintaxis: un parámetro sin un valor por defecto, sigue a un parámetro con un valor por defecto
Lo que el error indica, es que no se puede poner un parámetro sin valor por defecto, después de cualquier otro que sí lo tenga. El orden correcto será primero los parámetros normales, y luego los que tienen valores por defecto.
Aquí tienes un ejemplo de uso correcto:
def suma(a, b=56):
return a + b
resultado = suma(10)
print(f"El resultado es: {resultado}.")
El resultado es: 66.
Si quieres pasar otro valor que no sea el que viene por defecto, para el parámetro por defecto, hazlo indicándolo posicionalmente:
resultado = suma(10,103)
El resultado es: 113.
O también, con argumentos de clave en el orden que quieras:
resultado = suma(b=10,a=103)
Python es muy flexible con las funciones.
A los argumentos por defecto se les denomina en inglés como default arguments.
Espacio publicitario
Argumentos arbitrarios posicionales: *args

Gracias a este tipo de argumentos, podremos crear
funciones que sirvan para obtener tantos argumentos
posicionales como queramos en una llamada a una función.
Para aplicar esta funcionalidad, solo tienes que declarar
como argumento esta palabra: *args
.
def crear_lista(*args):
# Creamos una lista vacía.
lista = []
# Añadimos los datos a la lista.
for dato in args:
lista.append(dato)
return lista
# Llamamos a la función.
llamada = crear_lista(7,45,32,134,563,23)
# Imprimimos la lista.
print("Los valores en la lista son:", llamada)
Los valores en la lista son: [7, 45, 32, 134, 563, 23]
En este código, gracias al parámetro especial
*args
, he podido pasar tantos valores como he
querido en la llamada de la función.
Aquí puedes ver lo que ocurre con varias llamadas de diferente número de argumentos:
· · · Resto código · · ·
# Llamamos a la función.
llamada_1 = crear_lista(7,45,32,134,563,23)
llamada_2 = crear_lista(65,67)
llamada_3 = crear_lista(6)
# Imprimimos la lista.
print(llamada_1)
print(llamada_2)
print(llamada_3)
[7, 45, 32, 134, 563, 23]
[65, 67]
[6]
Gracias a *args
, podemos crear funciones
mucho más flexibles y adaptables a diversas
circunstancias.
Del código anterior, fíjate en el bucle for
,
en él estoy iterando un elemento llamado
args
:
# Añadimos los datos a la lista.
for dato in args:
lista.append(dato)
Esto quiere decir que el parámetro args
es un
iterable y tiene posiciones de índice.
Vamos a hacer una prueba de tipo de dato:
def funcion(*args):
# Imprimimos la tupla generada
print(args)
# Comprobamos el tipo de dato que es
print(type(args))
funcion(10,20)
(10, 20)
<class 'tuple'>
Efectivamente, se trata de un iterable. En concreto de una tupla.
Espacio publicitario
Esta tupla almacena todos los argumentos que le pasamos en la llamada, así que en realidad este parámetro especial no es mágico, sino que en realidad, le pasamos un solo argumento, la tupla entera.
En este ejemplo, estoy emulando el comportamiento de
*args
, sin utilizarlo, claro:
def funcion(mi_args):
# Imprimimos la tupla generada
print(mi_args)
# Comprobamos el tipo de dato que es
print(type(mi_args))
tupla = (10,20)
funcion(tupla)
(10, 20)
<class 'tuple'>
En su lugar, he conseguido emularlo utilizando una tupla.
Esto es solo una emulación para que veas como funciona
internamente *args
. ¿Quiere decir que
*args
entonces es prescindible? No. El
parámetro especial *args
emplea un tupla con
cualquier valor que pasemos como argumento, por ejemplo,
que pasas un solo int
, *args
lo
gestiona y crea una tupla con un solo int
. Si
pasas 10
valores de cualquier tipo, hace lo
mismo, en cada llamada.
En conclusión, *args
te evita de tener que
estar creando tuplas y añadiendo los valores fuera de las
funciones.
*args como convención
*args
realmente es un nombre de convención.
Si pones cualquier otro nombre, funcionará. Lo que le
importa al intérprete, es que le pongas el asterisco antes
que el nombre.
Mira el siguiente ejemplo:
def funcion(*objetos):
# Imprimimos la tupla generada
print(*objetos)
funcion("hola", 34.6, 19, True)
hola 34.6 19 True
Si decides no seguir la convención de llamarlo
*args
, especifícalo muy bien en la
documentación, aunque realmente, quien sabe programar en
Python, ya conoce de inmediato que
*objetos
es lo mismo que *args
,
por el asterisco.
Espacio publicitario
La función predefinida print() de Python
A continuación tienes un fragmento resumido de algunos
parámetros que tiene la función print()
de
Python en su código interno:
def print(*values: object, sep:" ", end:"\n")
Fíjate en el primer parámetro (*values
).
Efectivamente, se trata del parámetro especial
*args
, solo que en esta función le han dado
el nombre de *values
, ya que eso es lo que
espera, una serie de valores. Podemos decir que es un
nombre semántico, que trata de describir con más precisión
lo que espera el parámetro.
Gracias a este primer parámetro, podemos pasar todos los
objetos y expresiones como queramos a la llamada de la
función print()
. Por ejemplo:
print("hola", 34.6, 19, True)
hola 34.6 19 True
*args siempre como último parámetro
Junto con el parámetro especial *args
, puedes
utilizar perfectamente argumentos de otros tipos. Puedes
apreciarlo en el siguiente ejemplo:
def funcion(a, b, *args):
print(a, b, args)
funcion(1, 4, 5, 23, 45)
1 4 (5, 23, 45)
El valor 1
y 4
, se pasan
posicionalmente a los parámetros a
y
b
respectivamente. Todos los argumentos que
vengan después, pasarán a formar parte de la tupla de
*args
.

Si añades un parámetro después de *args
e
intentas proporcionar un argumento posicional para este,
recibirás un error. Esto es porque todos los argumentos
posicionales se pasarán a *args
.
Aquí puedes ver un ejemplo:
def funcion(a, b, *args, c):
print(a, b, args, c)
funcion(1, 4, 5, 23, 45, 50)
TypeError: funcion() missing 1 required keyword-only argument: 'c'
Error de tipo: a la llamada de funcion(), le falta un argumento obligatorio de solo clave: 'c'
El error indica que en la llamada nos falta un argumento de tipo clave. Ahí nos revela lo que tenemos que hacer para conseguir utilizar la función de esta forma.
En el siguiente ejemplo, le doy un argumento por defecto
(de clave), para el parámetro c
.
def funcion(a, b, *args, c=10):
print(a, b, args, c)
funcion(1, 4, 5, 23, 45)
1 4 (5, 23, 45) 10
De esta forma, no hace falta pasar ese argumento. No da error.
Espacio publicitario
Si quieres pasar un argumento para el parámetro c en la llamada, lo tendrás que hacer como argumento de clave:
def funcion(a, b, *args, c=10):
print(a, b, args, c)
funcion(1,4,5,23,45,c=1500)
1 4 (5, 23, 45) 1500
A los argumentos arbitrarios posicionales se les denomina en inglés como arbitrary positional arguments
Expresiones como argumentos

Podemos pasar expresiones de todo tipo como argumentos. Estas se resolverán primero, y sus resultados serán pasados como argumentos en las llamadas de las funciones.
Aquí tienes un ejemplo:
def suma(a):
print(f"El resultado es: {a}.")
suma(10+20)
El resultado es: 30.
Aquí lo que ocurre es que se resuelve primero la expresión, y después se pasa el resultado como argumento posicional, que nos deja realmente una llamada como esta:
suma(30)
Estas expresiones se pueden utilizar también con los otros tipos de argumentos. Por ejemplo:
def resolver_expresion(a):
print(f"El resultado de la expresión es: {a}.")
resolver_expresion(a = 10 == 10)
El resultado de la expresión es: True.
En este ejemplo he utilizado un argumento de clave, y le he asignado una expresión booleana, que es lo mismo que hacer esto:
resolver_expresion(a = True)
Espacio publicitario
Argumentos arbitrarios de clave: **kwargs

Llegamos al segundo tipo de argumentos arbitrarios,
**kwargs
.
**kwargs
, es parecido a *args
.
La diferencia principal es que nos permite pasar un
diccionario de argumentos, en lugar de una tupla de
valores.
Esto nos será útil para trabajar con argumentos de clave arbitrarios, en las llamadas de las funciones.
Por ejemplo, podemos hasta construir un diccionario mediante argumentos de clave, con cuantos pares clave-valor queramos:
def diccionario(**kwargs):
print(kwargs)
diccionario(nombre="Gabriela",
apellidos="Gómez de la barca",
edad="27")
{'nombre': 'Gabriela', 'apellidos': 'Gómez de la barca', 'edad': '27'}
**kwargs como convención
**kwargs
, también es un nombre de convención.
Lo importante para que el intérprete de Python entienda
que quieres usarlo, es especificar los dos asteriscos
(**
). El resto puede llamarse como quieras:
def diccionario(**pares):
print(pares)
diccionario(nombre="Gabriela",
apellidos="Gómez de la barca",
edad="27")
¿Se puede utilizar *args junto con **kwargs?
Sí, se puede utilizar. Aquí tienes un ejemplo:
def datos(*args, **kwargs):
print(args)
print(kwargs)
usuario1 = {"nombre":"Gabriela",
"apellidos":"Gómez de la barca",
"edad":"27"}
datos(10,50,60, **usuario1)
(10, 50, 60)
{'nombre': 'Gabriela', 'apellidos': 'Gómez de la barca', 'edad': '27'}
Primero, le damos todos los argumentos que necesitemos en
la llamada, la parte del *args
. Después, se
le pasa un diccionario al parámetro **kwargs
.
El intérprete de Python “entiende” lo que queremos hacer.
Espacio publicitario
Debes seguir este orden, primero *args
y
luego **kwargs
. Si lo pones al revés,
produces un error:
def datos(**kwargs, *args):
SyntaxError: arguments cannot follow var-keyword argument
Error de sintaxis: los argumentos no pueden seguir a un argumento de clave variable (**kwargs)
Cuando utilices Python para un propósito concreto,
encontrarás un montón de usos para **kwargs
.
Por ejemplo, si trabajas con bases de datos, podrías crear
un diccionario que llevara los datos de conexión, como
este:
conexion = {
"host": "localhost",
"user": "root",
"password": "1234",
"database": "bd",
}
Este diccionario se le pasaría con **kwargs como
argumento, a una función preparada para conectar con un
servidor como el de MySQL
.
Diccionarios y el método items()
Con los diccionarios tenemos un método muy práctico para
recibir las claves y los valores de una forma muy curiosa.
Estoy hablando del método items()
.
En este ejemplo puedes ver el tipo de dato y el valor almacenado en la variable:
argumentos = {"Nombre": "Enrique",
"Edad": 32,
"Telefono": "123456789"}
print(type(argumentos.items()))
print(argumentos.items())
<class 'dict_items'>
dict_items([('Nombre', 'Enrique'), ('Edad', 32), ('Telefono', '123456789')])
Este método nos devuelve un objeto especial que se suele
denominar como objeto de vista iterable. Estoy hablando de
dict_items
. El objeto contiene una lista con
tuplas con cada par clave-valor de un diccionario.
Gracias al uso de dos iteradores, podemos iterar con un solo bucle, cada parte individual de cada par.
Bucle con doble iterador para **kwargs

Algo que harás muy a menudo con **kwargs, es la
utilización de items()
y dos iteradores en un
mismo bucle.
Espacio publicitario
Por ejemplo:
def info(**kwargs):
for clave, valor in kwargs.items():
print(f"Clave: {clave}, Valor: {valor}")
argumentos = {"Nombre": "Enrique",
"Edad": 32,
"Teléfono": "123456789"}
info(**argumentos)
Clave: Nombre, Valor: Enrique
Clave: Edad, Valor: 32
Clave: Teléfono, Valor: 123456789

En el ejemplo que acabas de ver, estoy pasándole a la
función el diccionario llamado argumentos
.
En el bucle for
tengo los iteradores
clave
y valor
separados por una
coma.
El bucle empieza a iterar el objeto
dict_items
.
En la primera ejecución entramos en la primera tupla. Se
le pasa la clave "Nombre"
al iterador
clave
, y el valor "Enrique"
al
iterador valor
.
Hará lo mismo con cada tupla que haya en el objeto
dict_items
.
A los argumentos arbitrarios de clave se les denomina en inglés como arbitrary keyword arguments.
Funciones lambda en Python

Una función lambda
, también conocida como
función anónima, es un tipo de función especial de Python,
que permite crear funciones simples con tantos parámetros
como quieras, pero con una única expresión.
El término lambda se escribe igual en español.
Espacio publicitario
Sintaxis de las funciones lambda
La sintaxis de este tipo de funciones es muy simple.
Utilizamos la palabra reservada lambda
, le
damos una serie de parámetros, dos puntos
(:
), y luego, la expresión que queramos.
Recuerda, solo una.
lambda parámetros : expresión
Funciones lambda vs. def
Las funciones lambda
son un tipo de función
muy práctica para utilizarse en operaciones sencillas.
Gracias a ellas, conseguiremos crear ciertas funciones en una sola línea, en lugar de ocupar varias.
En el siguiente ejemplo tienes dos veces la misma función,
una escrita como función lambda
y la otra
como función normal (def
).
Función lambda:
lambda x, y: x + y
Función normal:
def sumar(x, y):
return x + y
Ambas funciones sirven para sumar dos valores. Una tiene
nombre y la otra no. Una ocupa dos líneas y la otra una.
Además, la función anónima devuelve automáticamente el
valor de retorno, sin utilizar la palabra
return
.
Bajo mi punto de vista, la función normal es más legible. Vemos más claro a simple vista todos los “pasos” que da.
Llamar a una función lambda
Quizás hayas caído, en que una función anónima, no tiene nombre. Por eso se le llama así.
Espacio publicitario
Entonces, si no tiene nombre, ¿cómo la vamos a llamar para utilizarla?
Realmente, podríamos decir que sí que tienen nombre, ya que para utilizarlas, las tendremos que guardar en una variable (no siempre es necesario), para así almacenar un valor de retorno, y poder “llamarlas”.
En el siguiente ejemplo, se asocia la función lambda a una
variable llamada suma
:
suma = lambda x, y: x + y
print(suma(5, 20))
print(suma(3, 7))
25
10
La variable suma
está sirviendo como nombre
para llamar a la función anónima.
En este caso, solo se están imprimiendo los valores de retorno, pero puedes almacenarlos en otras variables:
suma = lambda x, y: x + y
operacion_1 = suma(5, 20)
operacion_2 = suma(3, 7)
print(operacion_1)
print(operacion_2)
25
10
¿Cuándo y por qué utilizar funciones lambda?
Las funciones anónimas se utilizan normalmente en situaciones donde una función normal sería excesiva o innecesaria.
También habrá veces que podrás facilitar mucho el código,
utilizando una función lambda
. Evitando
incluso la anidación de elementos.
Por ejemplo, cuando creamos eventos en ciertas situaciones, como con las interfaces gráficas de la biblioteca Tkinter, nos pueden ser de gran utilidad.
A medida que utilices propósitos específicos en Python, empezarás a ver donde y como utilizar estas funciones anónimas.
Al principio pueden parecer algo abstractas y carecer de sentido; hace falta práctica para ir conociéndolas mejor.
Recuerda: Una función anónima es igual que una normal (
def
), con la diferencia de que se crea en una sola línea, con una sola expresión, que devuelve los valores de retorno sinreturn
y que no tiene nombre.
Espacio publicitario
Ejemplo práctico con lambda
La siguiente función def
, es capaz de obtener
un conjunto entero de números y devolver el resultado de
la raíz cuadrada de cada número, en una lista:
# Calcula raíces cuadradas y las devuelve en una lista
def raices_cuadradas(numeros):
def raiz_cuadrada(numero):
return numero ** 2
raices = map(raiz_cuadrada, numeros)
return list(raices)
# Lista para probar la función
lista_numeros = [9, 15, 150, 63, 70]
# Se almacena la lista devuelta por la función
resultado = raices_cuadradas(lista_numeros)
# Comprobamos los valores
print(resultado)
[81, 225, 22500, 3969, 4900]
Para que entiendas a la perfección como funciona esta función, voy a detallar cada parte.
Fórmula de cálculo de una raíz cuadrada

El cálculo de una raíz cuadrada es sencillo. Se representa frecuentemente con la fórmula de la imagen, no obstante, se puede expresar de forma más entendible:
Donde x
, es el resultado de
multiplicar un número al cuadrado.
Este cálculo se puede también con el operador de potencia
de Python (**
):
numero ** 2
Precisamente, este es el cálculo que se aplica en la
función anidada raiz_cuadrada()
del ejemplo
anterior.
La función externa raices_cuadradas()
se
encarga de calcular todas las raíces cuadradas, utilizando
para ello, la función interna o anidada, que calcula y
devuelve una sola raíz cuadrada cada vez que es llamada.
La función predefinida map()
La función predefinida map()
de Python,
permite llamar a una función de forma automática sobre un
iterable. Esta devolverá finalmente un nuevo objeto
iterable de tipo map
.
Espacio publicitario
Con map()
tenemos esta sintaxis:
map(funcion, objeto iterable)
En la función externa raices_cuadradas()
,
utilizo map()
con la función interna, y el
objeto numeros
, que es el objeto que se pasa
como argumento en la llamada de la función externa.
raices = map(raiz_cuadrada, numeros)
En este caso, el objeto pasado al parámetro
numeros
es la lista
lista_numeros
:
resultado = raices_cuadradas(lista_numeros)
Al ejecutarse la función map()
, llama a la
función interna, pasándole de argumento para
numero
, el primer valor iterado en el objeto
iterable (lista_numeros
en el ejemplo).
Con esto, vamos confeccionando una lista con los valores calculados de las raíces cuadradas, haciendo tantas llamadas como elementos tenga el iterable pasado.
Finalmente, tenemos un objeto iterable de tipo
map
, que podemos transformar a otro iterable,
como puede ser una lista. Esto lo hace la función aquí:
return list(raices)
Devuelve una lista con cada cálculo.
Puesto que es un valor devuelto en la llamada, lo capturamos en una variable:
resultado = raices_cuadradas(lista_numeros)
Así pues, ya tenemos en la variable resultado una lista
con las raíces cuadradas calculadas a partir de los
números de la lista inicial (lista_numeros
).
Veamos un ejemplo más simple con map()
:
# Calcula la raíz cuadrada
def raiz_cuadrada(numero):
return numero ** 2
# Lista con números
lista_numeros = [9, 15, 150, 63, 70]
# Hacemos los cálculos
resultado = map(raiz_cuadrada, lista_numeros)
# Comprobamos el tipo de dato que crea map
print(type(resultado))
# Creamos la lista con los cálculos
lista_raices = list(resultado)
# Comprobamos el resultado
print(lista_raices)
<class 'map'>
[81, 225, 22500, 3969, 4900]
La función raiz_cuadrada()
solo es capaz de
devolver una raíz cuadrada de un solo número.
# Calcula la raíz cuadrada
def raiz_cuadrada(numero):
return numero ** 2
La funcion w obtiene la función que queremos utilizar, y
el iterable lista_numeros
.
# Hacemos los cálculos
resultado = map(raiz_cuadrada, lista_numeros)
Si imprimimos el tipo de objeto que crea
map()
, en la consola nos aparece clase
map
:
# Comprobamos el tipo de dato que crea map
print(type(resultado))
<class 'map'>
Un objeto de tipo map
, es un iterador. Este
iterador se puede transformar a lista, conjunto, tupla,
etc. En concreto, lo hago aquí:
# Creamos la lista con los cálculos
lista_raices = list(resultado)
Le paso a la función conversora list()
, el
objeto de tipo map
, que lo que hace es añadir
cada valor en una posición de la lista.
Este ejemplo es más fácil de entender, pero no implementa la solución completa en una función reutilizable, por lo tanto, es una solución poco óptima si queremos utilizar esta calculadora de raíces en más módulos o en otras partes de la misma hoja de código.
Espacio publicitario
Facilitando el código con una función lambda
Esta es una de las buenas ocasiones para utilizar una
función lambda
, en lugar de una función
def
(la función lambda
está marcada en el
código):
# Procesa listas de números y devuelve la raíz cuadrada
def raices_cuadradas(numeros):
cuadrados = map(lambda numero: numero ** 2, numeros)
return list(cuadrados)
# Lista para probar la función
lista_numeros = [9, 15, 150, 63, 70]
# Se crea la lista con las raices cuadradas calculadas
raices = raices_cuadradas(lista_numeros)
# Comprobamos los valores
print(raices)
[81, 225, 22500, 3969, 4900]
El resultado es el mismo, pero ya no necesitamos tanta
complicación. Con solo tres líneas de código, estamos
haciendo todo lo que hacíamos con el sistema de funciones
def
anidadas.
Le pasamos a la función raices_cuadradas()
un
conjunto de números. En el ejemplo, es la lista
lista_numeros
:
raices = raices_cuadradas(lista_numeros)
La función map()
lleva la funcion
lambda
como argumento de función. Esta
lambda
lleva la única expresión necesaria
para el cálculo, que es el de cálculo de las raíces
cuadradas.
cuadrados = map(lambda numero: numero ** 2, numeros)
Como segundo argumento para la función map()
,
tenemos el objeto numeros
, que es la lista
lista_numeros
pasada como argumento a la
llamada de la función raices_cuadradas()
:
cuadrados = map(lambda numero: numero ** 2, numeros)
. . .
lista_numeros = [9, 15, 150, 63, 70]
raices = raices_cuadradas(lista_numeros)
Entonces, la función va iterando cada valor de la lista pasada, aplicándole la fórmula y devolviendo el resultado.
Como podrás comprobar, en este caso concreto, el uso de
una función lambda
, nos ha ahorrado utilizar
la anidación, que siempre que sea posible, es mucho mejor
no utilizarla, para evitar añadir complejidad extra al
código.
Espacio publicitario
Puede que te cueste asimilar este comportamiento al principio, pero ves analizándolo detenidamente. Estamos tratando cosas que pueden considerarse bastante complejas, para quien está aprendiendo todavía la base de Python.
No te frustres si no entiendes algo, o si crees que no serás capaz de aplicarlo más adelante a tus programas. Todo llega con la práctica.
El término map en español se traduce como mapa.
Funciones decoradoras con Python

Las funciones decoradoras de Python son funciones especiales que nos ayudarán a “decorar” el código de ciertas funciones. Se trata de emplear ciertas funcionalidades de una función sobre otras. Esto quedará más claro con los ejemplos que verás a continuación.
Función decoradora se traduce al inglés como decorator function.
Las funciones son objetos
En Python, las funciones también son objetos; puedes comprobarlo de esta forma:
def funcion():
pass
print(type(funcion))
<class 'function'>
Esto entre muchas posibilidades, es uno de los motivos por los cuales podemos asignar una función a una variable.
Llamadas como argumentos
También podemos pasar funciones como argumentos para otras funciones:
def a():
print("Función A ejecutada")
def b(funcion):
print("Función B ejecutada")
funcion() # Llama a la función que se pasa como argumento
# Llamada a la función b pasando la función a como argumento
b(a)
Función B ejecutada
Función A ejecutada
En este ejemplo, la función b()
espera un
argumento. Este argumento podría ser cualquier objeto. Sin
embargo, puesto que dentro de la función se hace uso del
argumento con unos paréntesis, se espera un objeto
invocable (callable), como una función.
Espacio publicitario
Si intento pasar un objeto que no se puede llamar, como
puede ser un int
, ocurre el siguiente error:
def a():
print("Función A ejecutada")
def b(funcion):
print("Función B ejecutada")
funcion() # Llama a la función que se pasa como argumento
# Llamada a la función b pasando la función a como argumento
b(10)
TypeError: 'int' object is not callable
Error de tipo: el objeto de tipo 'int' no es invocable
El error ocurre en la llamada que intenta hacer la función
b()
, ya que está intentando llamar a unint
, cosa que no tiene sentido y que hace que la llamada a la función pasada como argumento, origine el error.
En el siguiente código, podemos observar que dentro de la
función b()
, es posible controlar
perfectamente el flujo de ejecución y hacer lo que
queramos. Tanto antes, como después de llamar a la función
pasada como argumento. Fíjate bien en la salida en la
consola:
def a():
print("Función A ejecutada")
def b(funcion):
print("Función B ejecutada")
funcion() # Llama a la función que se pasa como argumento
# Llamada a la función b pasando la función a como argumento
b(a)
Función B ejecutada
Función A ejecutada
La función b()
ejecuta su
print()
y luego llama a la función parámetro
(la que le pasamos al parámetro funcion
).
Este orden no es definitivo, podemos poner código en la posición que queramos. Por ejemplo:
def a():
print("Función A ejecutada")
def b(funcion):
print("Se ha iniciado la función")
funcion() # Llama a la función que se pasa como argumento
print("Función B ejecutada")
# Llamada a la función b pasando la función a como argumento
b(a)
Se ha iniciado la función
Función A ejecutada
Función B ejecutada
Gracias a entender el flujo de ejecución, puedo controlar en todo momento que se ejecuta antes y qué se ejecuta después. En las tres líneas de la salida de la consola anterior, la ejecución viene dada de esta forma:
-
print("Se ha iniciado la función")
- Código deb()
. -
funcion()
- Ejecuta el código dea()
. -
print("Función B ejecutada")
- Código deb()
.
Espacio publicitario
Aplicando decoradores

Veamos un nuevo ejemplo. Voy a crear cuatro funciones de operaciones aritméticas. Hasta aquí, serán funciones normales y corrientes.
def sumar():
print(10 + 10)
def restar():
print(10 - 20)
def multiplicar():
print(45 * 2)
def dividir():
print(4 / 87)
Soy consciente de que no son funciones útiles, al tener simplemente un resultado fijo, pero quiero simplificar el concepto de las funciones decoradoras al máximo, después, ya habrá tiempo de complicarlo.
Ahora supón que les quieres agregar más funcionalidad, añadiéndoles alguna frase antes de realizar la operación, y otra después:
def sumar():
print("El resultado de la operación es: ")
print(10 + 10)
print("Operación realizada con éxito.")
def restar():
print("El resultado de la operación es: ")
print(10 - 20)
print("Operación realizada con éxito.")
def multiplicar():
print("El resultado de la operación es: ")
print(45 * 2)
print("Operación realizada con éxito.")
def dividir():
print("El resultado de la operación es: ")
print(4 / 87)
print("Operación realizada con éxito.")
El código de estas funciones se está repitiendo mucho,
tanto el primer print()
como el tercero, son
exactamente iguales en las cuatro funciones ¿Y si en lugar
de cuatro, tienes cien funciones de este tipo?
Aquí es donde entran en juego las funciones decoradoras; así que voy a crear una para este propósito:
def decoradora(funcion_parametro):
print("El resultado de la operación es: ")
funcion_parametro()
print("Operación realizada con éxito.")
@decoradora
def sumar():
print(10 + 10)
def restar():
print(10 - 20)
def multiplicar():
print(45 * 2)
def dividir():
print(4 / 87)
El resultado de la operación es:
20
Operación realizada con éxito.
En este caso, estoy decorando solo la función
sumar()
, con la función decoradora llamada
@decoradora
.Lo que se consigue con esto, es
pasar la función sumar()
a la función
decoradora, como su parámetro.
El comportamiento es el mismo que en los ejemplos
anteriores, pero ahora, no tenemos que hacer una llamada
explícita en el código. De esto se encarga el decorador
@decoradora
.
Espacio publicitario
Por la salida en la consola podemos apreciar que primero
aparece el primer print()
de la funcion
decoradora.
A continuación se llama a la función pasada como argumento
(sumar()
), y finalmente se imprime la frase
con el segundo print()
de la función
decoradora.
Es posible aplicar este código repetitivo a cuantas
funciones necesitemos. Tan solo hay que utilizar el nombre
de la función decoradora con una @
de
prefijo.
Por ejemplo:
def decoradora(funcion_parametro):
print("El resultado de la operación es: ")
funcion_parametro()
print("Operación realizada con éxito.")
@decoradora
def sumar():
print(10 + 10)
@decoradora
def restar():
print(10 - 20)
@decoradora
def multiplicar():
print(45 * 2)
@decoradora
def dividir():
print(4 / 87)
El resultado de la operación es:
20
Operación realizada con éxito.
El resultado de la operación es:
-10
Operación realizada con éxito.
El resultado de la operación es:
90
Operación realizada con éxito.
El resultado de la operación es:
0.04597701149425287
Operación realizada con éxito.
Está funcionando, nos ahorramos repetir los dos
print()
con cada función. Sin embargo,
tenemos un gran problema. Las funciones decoradoras, como
la que estamos construyendo, se ejecutan automáticamente,
sin llamarlas en el código.
Por lo tanto, con una función decoradora como la del ejemplo, estamos implementando mal el sentido de estas funciones. A continuación verás una estructura más funcional.
Espacio publicitario
Estructura de una función decoradora de Python
La estructura típica de una función decoradora de Python
se basa en tres funciones, las cuales, con el fin de
entendernos de forma simple, llamaré
decoradora()
,
funcion_parametro()
e
interna()
(estos nombres no son
obligatorios).
La función decoradora()
va a ser la que
reciba como argumento la función
funcion_parametro()
. A raíz de esto, la
función decoradora()
, devolverá con un
return
el valor de llamar a la función
interna()
. Esto parece una locura, pero verás
que es más sencillo de lo que parece.
Sintaxis de una función decoradora de Python
Esta es la sintaxis de una función decoradora de Python:
def decoradora(funcion_parametro):
def interna():
#código de la función interna
return interna
Piensa, que el
return
está indentado dentro de la funcióndecoradora()
, no en lainterna()
.
Otra forma de representar esto es la siguiente:
def a(b) -> c
Aquí puedes ver que la función a()
tiene la
función b()
como parámetro y devuelve la
función c()
.
En este ejemplo, estos nombres se corresponden con el otro ejemplo, de la siguiente forma:
a()
: Función decoradorab()
: Función parámetro o decoradac()
: Función interna
Empecemos por ejecutar este código:
def decoradora(funcion_parametro):
def interna():
print("El resultado de la operación es: ")
funcion_parametro()
print("Operación realizada con éxito.")
@decoradora
def sumar():
print(10 + 10)
Esta vez no se está llamando a la función decorada directamente. La función interna ha contenido la llamada.
Sin embargo, si llamas a la función sumar()
,
te toparás de bruces con un problema:
sumar()
TypeError: 'NoneType' object is not callable
Error de tipo: el objeto de tipo 'NoneType' no es invocable
Al decorar la función, la hemos convertido en un elemento de tipo
NoneType
que no es invocable ¡No podemos llamar a la función!
No nos alarmemos y busquemos una solución.
Esto ocurre, porque la llamada está contenida en la función interna; recuerda que teníamos un alcance encerrado en las funciones anidadas.
Para solucionarlo, escribimos un return
:
def decoradora(funcion_parametro):
def interna():
print("El resultado de la operación es: ")
funcion_parametro()
print("Operación realizada con éxito.")
return interna
@decoradora
def sumar():
print(10 + 10)
sumar()
El resultado de la operación es:
20
Operación realizada con éxito.
Ahora sí, problema resuelto.
Espacio publicitario
Las funciones decoradoras con parámetros

Voy a complicar un poco más todo este asunto, añadiendo parámetros a las funciones decoradoras.
Como puedes apreciar, las funciones del ejemplo anterior, son más bien inútiles, ya que siempre van a dar los mismos resultados, y para eso, no haría ni falta que fueran funciones.
Con el fin de hacer estas funciones prácticas, podemos añadir parámetros:
def sumar(num1, num2):
print(num1 + num2)
Si llamamos a esta función sin utilizar decoradores, no hay problema alguno:
sumar(40,54)
sumar(100,305)
sumar(48,52)
94
405
100
Pero, ¿y si intentamos decorarla?
def a(b):
def c():
print("El resultado de la operación es: ")
b()
print("Operación realizada con éxito.")
return c
@a
def sumar(num1, num2):
print(num1 + num2)
sumar(40,54)
sumar(100,305)
sumar(48,52)
TypeError: a.<locals>.c() takes 0 positional arguments but 2 were given
Error de tipo: La función c() definida en a() toma 0 argumentos posicionales, pero se le pasaron 2.
El error me está diciendo que en la función interna (la
c()
) que lleva la función decoradora (laa()
), estoy pasando dos argumentos posicionales (num1
ynum2
), pero espera cero.El error se desencadena con la primera llamada, pero las otras dos ocasionarían lo mismo, si el código no fallase antes de ni siquiera poder ejecutarlas.
Si miramos la función c()
, el error no se
equivoca; esta función no tiene parámetros para los que
pasar esos dos argumentos. Por lo tanto, ya sabemos donde
hay que colocar los parámetros; en la función
c()
, la interna:
def a(b):
def c(num1,num2):
print(f"El resultado de la operación es: ")
b()
print("Operación realizada con éxito.")
return c
@a
def sumar(num1, num2):
print(num1 + num2)
sumar(40,54)
sumar(100,305)
sumar(48,52)
TypeError: sumar() missing 2 required positional arguments: 'num1' and 'num2'
Error de tipo: La función sumar() requiere 2 argumentos posicionales 'num1' y 'num2'
Vaya, otro error. Ahora, el error está en la función
sumar()
, que desencadena el problema en la función parámetrob()
. El error indica que faltan 2 argumentos posicionales requeridos.
Espacio publicitario
Para solucionar esto, le añadimos los dos parámetros a la
llamada de la función b()
, y listo:
def a(b):
def c(num1,num2):
print(f"El resultado de la operación es: ")
b(num1, num2)
print("Operación realizada con éxito.")
return c
@a
def sumar(num1, num2):
print(num1 + num2)
sumar(40,54)
El resultado de la operación es:
94
Operación realizada con éxito.
Por fin, ha funcionado.
Es solo pasar los argumentos a la función interna, y esta se los pasa a los de la llamada de la función parámetro. Si alguna de las dos no tiene parámetros, no se le pueden pasar argumentos.
*args en las funciones decoradoras

El resultado está muy bien, pero ¿y si queremos utilizar el decorador en funciones que tengan un número indeterminado de parámetros?
Supongamos que en la función de resta, queremos operar con cuatro números y no con dos, pero en la de suma, queremos seguir operando con dos.
Con el fin de no tener que crear varias funciones
decoradoras para utilizar lo mismo (lo que les haría
perder todo el sentido), podemos utilizar el anteriormente
explicado *args
.
def a(b):
def c(*args):
print(f"El resultado de la operación es: ")
b(*args)
print("Operación realizada con éxito.")
return c
@a
def sumar(num1, num2):
print(num1 + num2)
@a
def restar(num1, num2, num3, num4):
print(num1 - num2 - num3 - num4)
sumar(30,50)
restar(40,54,60,43)
El resultado de la operación es:
80
Operación realizada con éxito.
El resultado de la operación es:
-117
Operación realizada con éxito.
Como puedes apreciar, ahora no importa el número de argumentos que pasemos; la función se adapta a ellos.
Espacio publicitario
*kwargs en las funciones decoradoras
Pasemos a otro ejemplo para ver la utilidad de emplear **kwargs en las funciones decoradoras de Python.
En el siguiente ejemplo, verás que hay funciones que esperan diferente número de argumentos; las dos primeras funciones, dos argumentos y la última, solo uno.
Pues bien, partiendo de la base, necesitamos poner
*args
si queremos decorar estas funciones:
import math
def a(b):
def c(*args):
print("Empieza el cálculo...")
b(*args)
print("Operación realizada con éxito.")
return c
@a
def area_rectangulo(base, altura):
print(f"El área del rectángulo es: {base * altura}.")
@a
def area_triangulo(base, altura):
print(f"El área del rectángulo es: {base * altura / 2}")
@a
def area_circulo(radio):
print(f"El área del círculo es {math.pi * radio ** 2}")
Hagamos una llamada, por ejemplo, con la función
area_rectangulo()
:
area_rectangulo(10,40)
Empieza el cálculo...
El área del rectángulo es: 400.
Operación realizada con éxito.
Funciona correctamente, al igual que lo harán las otras dos.
Sin embargo, mira lo que ocurre si quieres hacer una llamada con argumentos de clave:
area_rectangulo(altura=40,base=10)
TypeError: a.<locals>.c() got an unexpected keyword argument 'altura'
Error de tipo: La función c() definida en a() toma un argumento de clave inesperado, 'altura'.
Nos suelta un error diciendo que no se esperaba un argumento de clave; el primero que encuentra, que es
altura
.
Claro, esto ocurre porque se lo estoy pasando a
*args
. El parámetro especial
*args
, como bien expliqué anteriormente, solo
recibe argumentos posicionales. Es aquí, donde hay que
añadir la funcionalidad extra de **kwargs
a
nuestra función decoradora. Esto permitirá el uso de
argumentos de clave en las funciones decoradas:
import math
def a(b):
def c(*args, **kwargs):
print("Empieza el cálculo...")
b(*args, **kwargs)
print("Operación realizada con éxito.\n")
return c
@a
def area_rectangulo(base, altura):
print(f"El área del rectángulo es: {base * altura}.")
@a
def area_triangulo(base, altura):
print(f"El área del rectángulo es: {base * altura / 2}")
@a
def area_circulo(radio):
print(f"El área del círculo es {math.pi * radio ** 2}")
area_rectangulo(altura=40,base=10)
area_rectangulo(base=6,altura=10)
area_circulo(radio=2)
Empieza el cálculo...
El área del rectángulo es: 400.
Operación realizada con éxito.
Empieza el cálculo...
El área del rectángulo es: 60.
Operación realizada con éxito.
Empieza el cálculo...
El área del círculo es 12.566370614359172
Operación realizada con éxito.
Los espacios en la consola, entre los resultados de las llamadas, vienen de un salto de línea que he añadido dentro de la función decoradora. Te lo he dejado en el código marcado (
\n
).
Espacio publicitario
Funciones generadoras e iteradores

Las funciones generadoras son un tipo de función especial de Python, que permite dividir una ejecución de función, en partes más pequeñas, con el fin de aprovechar mejor los recursos del sistema, sobre todo hablando de memoria.
Esto es gracias a que estas funciones crean un objeto iterador que se puede ejecutar paso a paso, en lugar de todo a la vez, como ocurre con las funciones normales.
Se dice que estas funciones son de evaluación diferida, porque se van ejecutando de forma gradual, generando un valor cada vez, y no todos de golpe.
Si creas un generador y no lo utilizas, la función no se
ejecutará en absoluto. Solo se ejecutará cuando se
solicite el primer valor mediante la función
next()
o al usar el generador con, por
ejemplo, un bucle for
.
La palabra reservada yield
La palabra reservada yield
es parecida a
return
, pero no exactamente igual.
Una función con return
, o incluso con
print()
, se ejecuta íntegra en el momento de
la llamada, y luego devuelve o imprime un valor, dando por
finalizada la ejecución.
Las funciones normales procesan y almacenan todos los datos de una secuencia de una sola vez. Eso no es importante en funciones con consumos mínimos, pero se convierte en un problema a la hora de trabajar con una gran cantidad de datos.
Con yield
, tenemos una especie de
return
, ya que devuelve valores, pero es
capaz de hacerlo por pasos, como un bucle y sus
iteraciones. Veamos un pequeño ejemplo utilizando
return
:
def generadora():
for i in range(0, 100):
return i
Tenemos una función con un bucle, y queremos que esta nos
devuelva los valores iterando un rango. Sin embargo, si
imprimimos el valor de retorno, veremos que después de
ejecutar el primer valor del
range()
(0
), finaliza la
ejecución; como era de esperar:
def generadora():
for i in range(0, 100):
return i
print(generadora())
0
Entonces, en este caso, si queremos imprimir todo el rango
lo tendríamos que hacer mediante un print()
:
def generadora():
for i in range(0, 100):
print(i)
generadora()
0
1
2
. . .
99
O bien, nos las ingeniamos para crear una lista o algo por el estilo, que guarde todos los valores y los pueda devolver de una sola vez:
def generadora_numeros():
lista_valores = []
for i in range(0, 100):
lista_valores.append(i)
return lista_valores
valores = generadora_numeros()
print(valores)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Mira lo que pasa utilizando yield
, e
imprimiendo el objeto producido en la llamada:
def generadora():
for i in range(0, 100):
yield i
print(generadora())
<generator object generadora at 0x00000225107FC520>
Recibimos la referencia del objeto, no un valor de retorno.
Espacio publicitario
Desde el momento en el que llamas a la función, esta queda con su objeto creada, esperando a ser iterada.
Claro, que si generas el objeto en un
print()
, no lo vas a poder usar en el código.
Entonces, vamos a guardar el objeto en una variable; así lo podremos manejar:
def generadora():
for i in range(0, 100):
yield i
rango = generadora()
Ahora, vamos a ir iterando el objeto gracias a la función
predefinida next()
de Python. Esta hará que
se ejecute un paso de la función cada vez:
print(next(rango))
print(next(rango))
print(next(rango))
0
1
2
Bien, así hasta que ya no quede rango que iterar.
La ventaja de esto, es que en funciones que manejen grandes volúmenes de datos, podremos dosificar la ejecución, para evitar hacerlo todo de golpe. Entre las ejecuciones, podríamos hacer cualquier cosa, por ejemplo:
def generadora():
for i in range(0, 100):
yield i
rango = generadora()
print(next(rango))
print("Acciones independientes a la función.")
print(next(rango))
print(next(rango))
0
Acciones independientes a la función.
1
2
En resumen, gracias a yield
, vas pausando y
ejecutando una llamada a una función, con muchos fines,
pero el principal es un rendimiento más óptimo en
operaciones que requieren procesar muchos datos.
Espacio publicitario
Utilizar varios yield en la misma generadora
Veamos otro ejemplo utilizando varios
yield
en la misma función. Cada uno genera
una salida distinta:
def generadora():
for i in range(0, 10):
if i % 2 == 0:
yield f"{i} - par"
else:
yield f"{i} - impar"
rango = generadora()
print(next(rango))
print(next(rango))
print(next(rango))
print(next(rango))
0 - par
1 - impar
2 - par
3 - impar
El mismo ejemplo con return
, no podría ir
ejecutando diferentes resultados. En este código concreto,
el resultado será siempre el mismo, se ejecuta el
if
, y finaliza la ejecución de la función:
def generadora():
for i in range(0, 10):
if i % 2 == 0:
return f"{i} - par"
else:
return f"{i} - impar"
rango = generadora()
print(rango)
print(rango)
print(rango)
print(rango)
0 - par
0 - par
0 - par
0 - par
Piensa que con return
, no creamos un objeto
iterable necesariamente. En el caso del ejemplo, se
devuelve un str, que es iterable, pero no es un objeto
iterador.
Con lo de que no es un iterador estoy diciendo también,
que no puedes utilizar la función next()
:
def generadora():
for i in range(0, 10):
if i % 2 == 0:
return f"{i} - par"
else:
return f"{i} - impar"
rango = generadora()
print(type(rango))
print(next(rango))
<class 'str'>
TypeError: 'str' object is not an iterator
Error de tipo: el objeto 'str' no es un iterador
Antes de aparecer el error en la última línea, se imprime
que el tipo de objeto es str
, no un
generator
como ocurre con yield
:
rango = generadora()
print(type(rango))
<class 'generator'>
Espacio publicitario
Generar otros objetos iterables a partir de una función generadora
Es posible generar objetos iterables como una lista a
partir de un objeto de tipo generator
. Esto
se consigue con una de las funciones predefinidas de
conversión, como puede ser list()
:
def generadora():
for i in range(0, 10):
if i % 2 == 0:
yield f"{i} - par"
else:
yield f"{i} - impar"
rango = generadora()
# Se crea la lista a partir del objeto generator
lista_generador = list(rango)
print(lista_generador)
['0 - par', '1 - impar', '2 - par', '3 - impar', '4 - par', '5 - impar', '6 - par', '7 - impar', '8 - par', '9 - impar']
Con esto tenemos acceso a cada iteración mediante posiciones de lista. Por ejemplo, que necesitamos el resultado de dos iteraciones concretas, pues podemos hacer algo tan simple como acceder a dos posiciones de lista concretas:
def generadora():
for i in range(0, 10):
if i % 2 == 0:
yield f"{i} - par"
else:
yield f"{i} - impar"
rango = generadora()
lista_generador = list(rango)
print(lista_generador[7])
print(lista_generador[2])
7 - impar
2 - par
Excepción StopIteration
La función next()
produce una excepción,
cuando las posibles ejecuciones de la función generadora
se han terminado, y se intenta acceder a la siguiente.
Aquí tienes un ejemplo:
def generadora():
for i in range(0, 3):
if i % 2 == 0:
yield i
rango = generadora()
print(next(rango))
print(next(rango))
print(next(rango))
0 2
StopIteration
Detención de iteración
Description
Si no quieres producir esta excepción cuando te salgas del
rango, puedes controlarla con los bloques
try-except
; tema que verás muy pronto. Por el
momento, aquí tienes un ejemplo:
def generadora():
for i in range(0, 3):
if i % 2 == 0:
yield i
rango = generadora()
try:
print(next(rango))
print(next(rango))
print(next(rango))
except StopIteration:
print("La ejecución finalizó.")
0
2
La ejecución finalizó.
El último print()
hace saltar el bloque
except
, ya que sale del rango de iteraciones
posible.
Espacio publicitario
Espacio publicitario
Ejercicios de Python para resolver
68. Crea una función que permita introducir un
str
en la consola, y que cuente el número de
caracteres que tiene.
La función deberá avisar con un print()
, con
el total de caracteres.
La función que he creado, tiene una variable que
cuenta la longitud del str
pasado como
argumento, gracias a la función len()
.
Además, formatea una frase que indica la longitud de caracteres.
def longitud_str():
# Almacena la entrada
cadena = input("Escriba algo y se lo cuento: ")
# Cuenta los caracteres de la entrada
numero_caracteres = len(cadena)
# Presenta el resultado en la consola
print(f"La cadena tiene una longitud de {numero_caracteres} caracteres.")
longitud_str()
Escriba algo y se lo cuento: Python: El poder de los objetos.
La cadena tiene una longitud de 32 caracteres.
69. Modifica la función anterior para que solo devuelva el número de caracteres. Después, opcionalmente, puedes hacer lo que quieras con ello.
Este ejercicio era para comprobar si sabes utilizar los valores de retorno.
def longitud_str():
# Almacena la entrada
cadena = input("Escriba algo y se lo cuento: ")
# Cuenta los caracteres de la entrada
numero_caracteres = len(cadena)
# Devuelve el resultado
return numero_caracteres
longitud_cadena = longitud_str()
print(longitud_cadena)
Escriba algo y se lo cuento: Python: El poder de los objetos.
32
En los siguientes ejercicios, quiero que crees una función iteradora con un bucle dentro.
70. Crea una función para iterar un rango pasado en los argumentos. Esta función aceptará dos argumentos. Uno para el número de inicio y otro para el del final.
Deja la función con pass
hasta el siguiente
ejercicio.
Creamos la función con dos argumentos, dejándola de
momento con pass
:
def iterar_rango(inicio, fin):
pass
71. Escribe un bucle for
, que itere el rango
pasado. Para ello, deberás utilizar una función
predefinida de Python destinada a ello.
El bucle deberá imprimir los valores del iterador en cada
ciclo (i
).
Se le añade el bucle con un range()
para
iterar el rango de inicio y fin, con un
print()
dentro, que irá mostrando el
valor del iterador en cada ciclo del bucle:
def iterar_rango(inicio, fin):
for i in range(inicio, fin):
print(i)
72. Realiza la llamada con los valores de inicio y fin que quieras.
Llamamos al método con un rango cualquiera, por
ejemplo, 10
y 15
:
def iterar_rango(inicio, fin):
for i in range(inicio, fin):
print(i)
iterar_rango(10, 15)
10
11
12
13
14
73. El rango que sale (por el funcionamiento de
range()
), no es exactamente el mismo que le
ponemos de inicio y fin. Por ejemplo, pones 10 y 15
respectivamente, y lo que sale en la consola es 10 hasta
14.
Soluciona el problema, y haz que el rango se imprima exactamente igual que en la llamada.
Deberás incrementar algo en 1.
Este ejercicio era para que fueses probando donde
poner un incremento en 1
, como indicaba
la pista. Si le sumas 1
al valor de fin,
consigues que si los valores pasados en la llamada son
10
y 15
, realmente, serán
10
y 16
, por lo que un
range(10, 16)
, irá del 10
al
15
, obteniendo el resultado deseado:
def iterar_rango(inicio, fin):
for i in range(inicio, fin + 1):
print(i)
iterar_rango(10, 15)
10
11
12
13
14
15
Esta función puede parecer poco práctica, y puede que
pienses por qué no utilizar la función
range()
directamente.
Piensa que con esta función, no tienes que escribir más el bucle, y que gracias a los argumentos, podemos llamarla en líneas separadas o en módulos diferentes, con valores diferentes, sin tener que escribir un bucle cada vez.
Las funciones están creadas para poder reutilizar código de forma fácil.
Espacio publicitario
74. De la siguiente función, ¿sabrías decir cuál de los
dos parámetros corresponde a args
, y cuál a
kwargs
?
def funcion(*a, **b):
¿Por qué lo has sabido en cada caso?
El parámetro *a
se corresponde con
*args
, y el parámetro
**b
con **kwargs
.
La forma de saberlo, es que args
se
representa con un asterisco, y kwargs
con
dos.
75. Crea una función que reciba un número indefinido de argumentos. Esta función servirá para introducir varios nombres en una lista.
Finalmente, la función deberá devolver la lista con todo.
Para hacer esto, dentro de la función, crea una lista vacía.
A continuación, un bucle que itere los nombres pasados como argumentos, y los vaya añadiendo a la lista vacía con un método para listas.
Primero, obtenemos un número de argumentos indefinido
gracias al uso de *args
.
Dentro de la función, tengo una lista vacía con la que ir incluyendo los diferentes nombres pasados como argumentos.
Esto lo hace el bucle con el método de
list
llamado append()
, que
itera el conjunto de argumentos pasados en la llamada,
y los va añadiendo a la lista.
Finalmente, se devuelve la lista completa con un
return
.
def obtener_personas(*nombres):
personas = []
for nombre in nombres:
personas.append(nombre)
return personas
lista_nombres = obtener_personas("Roberto", "Marga", "Raúl")
print(lista_nombres)
['Roberto', 'Marga', 'Raúl']
76. ¿Sabes utilizar esta función?
def imprimir_valores(**kwargs):
for clave, valor in kwargs.items():
print(f"{clave}: {valor}")
Haz una llamada con las siguientes claves (los valores son libres).
Nombre
Apellidos
Edad
Ciudad
Esta es una posible llamada a la función. Aquí solo
tenías que demostrar que sabes utilizar una función
con **kwargs
.
def imprimir_valores(**kwargs):
for clave, valor in kwargs.items():
print(f"{clave}: {valor}")
imprimir_valores(Nombre = "Enrique",
Apellidos = "Barros Fernández",
Edad = 32,
Ciudad = None
)
Nombre: Enrique
Apellidos: Barros Fernández
Edad: 32
Ciudad: None
77. Crea una función de multiplicación con dos argumentos por defecto.
Los parámetros serán a
, con un valor de
argumento por defecto 50
, y
b
con un valor por defecto de
70
.
Haz que devuelva el resultado, y guarda en variables las llamadas que te detallo a continuación.
Primero, haz una operación sin argumentos para comprobar que los argumentos por defecto, se están aplicando correctamente. Por ejemplo:
multiplicacion_1 = multiplicacion()
Después, haz otra llamada con uno solo. Por ejemplo:
multiplicacion_2 = multiplicacion(100)
Finalmente, haz una llamada con dos argumentos. Por ejemplo:
multiplicacion_3 = multiplicacion(10,700)
Si te devuelve correctamente los resultados (no hay excepciones ni fallos de lógica), es que la has hecho correctamente.
def multiplicacion(a=50, b=70):
return a * b
sin_argumentos = multiplicacion()
print(sin_argumentos)
un_argumento = multiplicacion(100)
print(un_argumento)
dos_argumentos = multiplicacion(10,700)
print(dos_argumentos)
3500
7000
7000
Espacio publicitario
Mini proyecto - Ticket de compra
Vas a realizar un pequeño proyecto dividido en varios
ejercicios. Se trata de crear una función generadora de
tickets, utilizando **kwargs
.
78. Crea una función que admita un número indefinido de
argumentos de clave, con **kwargs
.
Creamos la función. Puedes dejarle pass
,
para que no te dé un error por dejar el bloque vacío:
def generar_ticket(**kwargs):
pass
79. Crea dentro de la función, una variable para guardar el total de la compra.
Por defecto, el valor será 0
.
La variable total
, tiene que tener un
valor inicial de 0
:
def generar_ticket(**kwargs):
total = 0
80. En la función, imprime un título para el ticket. Aquí tienes una idea:
Ticket de compra:
Añadimos un título con un print()
:
def generar_ticket(**kwargs):
total = 0
print("Ticket de compra:")
81. Crea un bucle for
con dos iteradores:
producto
y precio
, como te he
mostrado en la parte teórica del capítulo.
El bucle en cada iteración deberá mostrar con un
print()
, el producto y el precio que le
corresponde.
Los datos más adelante los pasaremos con claves y valores en la llamada a la función. Estos serán argumentos de clave, que pueden usarse como diccionario, ya que tienen sus dos elementos, clave y valor.
Creamos el bucle con los dos iteradores. Así podemos iterar de una sola vez, clave y valor de los argumentos de clave.
def generar_ticket(**kwargs):
total = 0
print("Ticket de compra:")
for producto, precio in kwargs.items():
print(f"{producto}: ${precio}")
Se hace igual que en la parte teórica de este capítulo, pero esta vez, en lugar de pasarle el diccionario, le pasamos los argumentos de clave.
El reto de este ejercicio era para probar si conseguías adaptar algo conocido, con una variación.
82. Aprovecha el bucle for
para añadirle,
después de imprimir, un incremento del precio de
cada producto procesado,
a la variable total
, que está a 0
por defecto. Así irá haciendo
la cuenta del precio total.
Añadimos el incremento de precio a la variable total, así se van sumando los precios de los productos:
def generar_ticket(**kwargs):
total = 0
print("Ticket de compra:")
for producto, precio in kwargs.items():
print(f"{producto}: ${precio}")
total += precio
83. Finalmente, añade todas las decoraciones que quieras
con uno o varios print()
. Por ejemplo, líneas
separadoras:
print("----------------------------------")
Además, deberás imprimir al final del ticket el precio final de toda la compra.
Se imprime una decoración y el coste total de la compra:
def generar_ticket(**kwargs):
total = 0
print("Ticket de compra:")
for producto, precio in kwargs.items():
print(f"{producto}: ${precio}")
total += precio
print("----------------------------------")
print(f"Coste total: ${total}")
84. Podemos añadir un extra a la función. Un
return
, para que además de hacer todo lo
anterior, cada compra genere un valor de retorno con el
precio total. Añádelo.
Añadimos un simple return
fuera del
bucle, para tener el valor de retorno del total:
def generar_ticket(**kwargs):
total = 0
print("Ticket de compra:")
for producto, precio in kwargs.items():
print(f"{producto}: ${precio}")
total += precio
print("----------------------------------")
print(f"Coste total: ${total}")
return total
Espacio publicitario
Mini proyecto - Fase de pruebas
85. Crea una variable llamada compra_1
. Esta
variable contendrá una llamada a la función, con la
siguiente compra:
- Taza: $5
- Collar: $77.99
- Televisor: $439.79
- Tenedores: $3.99
Recuerda que la llamada debe ser con argumentos de clave, para poder devolver el objeto
dict_items
, que sea iterado por el bucle.
Hacemos la siguiente llamada, para probar la función:
compra_1 = generar_ticket(Taza = 5,
Collar = 77.99,
Televisor = 439.79,
Tenedores = 3.99
)
Ticket de compra:
Taza: $5
Collar: $77.99
Televisor: $439.79
Tenedores: $3.99
----------------------------------
Coste total: $526.77
86. Crea otra variable llamada compra_2
. Esta
variable contendrá una llamada a la función, con la
siguiente compra:
- Camiseta: $19.99
- Monitor: $199
- Libreta: $3.49
Si al juntar varias llamadas de compra, te salen en la consola los tickets de una forma que no te gusta, puedes añadir saltos de línea o cualquier otro elemento de presentación. Deja la salida como más te guste.
Probamos una llamada más:
compra_2 = generar_ticket(Camiseta = 19.99,
Monitor = 199,
Libreta = 3.49
)
Coste total: $526.77
Ticket de compra:
Camiseta: $19.99
Monitor: $199
Libreta: $3.49
----------------------------------
Coste total: $222.48000000000002
87. Habrá resultados que te salgan con muchos decimales (como es el caso del segundo ticket). Esto no sirve para cobrar a un cliente. Necesitamos que la función dé un precio final con máximo dos decimales, que se correspondan a los céntimos/centavos.
Simplemente, redondeamos a dos decimales, con la
función round()
:
def generar_ticket(**kwargs):
total = 0
print("Ticket de compra:")
for producto, precio in kwargs.items():
print(f"{producto}: ${precio}")
total += precio
print("----------------------------------")
print(f"Coste total: ${round(total, 2)}")
return total
88. Haz una simple suma con las variables
compra_1
, y compra_2
. Debería
darte el total de las dos compras. Si es así, te está
funcionando lo del valor de retorno.
Todavía hay muchas pruebas que podríamos realizar, por ejemplo, ¿qué ocurre al poner una letra de precio en lugar de un valor numérico en las llamadas?
Realizamos una suma de las dos compras, para comprobar si funciona el valor de retorno de la función:
suma_tickets = compra_1 + compra_2
print(f"La suma de las compras es: ${suma_tickets}")
Este programa, cuando hagas cosas con interfaces gráficas de usuario, podrá tener una serie de botones, estilo TPV (Punto de venta), que irán añadiendo los precios a un elemento como un diccionario, y se lo pasarán a la función de forma automática.
89. Convierte esta función para calcular el cuadrado de un
número, a función lambda
:
def cuadrado(a):
return a * a
No la utilices para nada aún. Solo declárala.
Esta es la adaptación de la función anterior, a
función lambda
:
lambda a : a * a
90. Guarda la función lambda
en una variable
llamada cuadrado
.
Guardamos la función lambda anterior en una variable
llamada cuadrado
:
cuadrado = lambda a : a * a
91. Haz un cálculo con la función lambda
(con
cualquier número) y visualiza el resultado en la consola
con un print()
.
Finalmente, llamamos a la función con cualquier valor numérico:
cuadrado = lambda a : a * a
print(cuadrado(10))
100
92. Crea una función normal de saludo. Simplemente, que al llamarla imprima un saludo en la consola.
Simplemente, creamos una función sencilla, sin parámetros:
def saludo():
print("Hola, espero que estés bien.")
93. Sin borrar la función anterior, crea una función decoradora que tenga un mensaje antes de llamar a la función decorada y otro después. El mensaje puede ser cualquier cosa.
Se crea una función decoradora sin parámetros de ningún tipo, que simplemente muestra un mensaje antes de ejecutar la función decorada, y otro después:
def decoradora(funcion_externa):
def funcion_interna():
print("Antes...")
funcion_externa()
print("Después...")
return funcion_interna
94. Decora la función de saludo con la función decoradora. Si te salen los tres mensajes al llamar a la función de saludo, lo has hecho perfecto.
def decoradora(funcion_externa):
def funcion_interna():
print("Antes...")
funcion_externa()
print("Después...")
return funcion_interna
@decoradora
def saludo():
print("Hola, espero que estés bien.")
saludo()
Antes...
Hola, espero que estés bien.
Después...
95. Ahora modifica la función decoradora, para que tenga la posibilidad de pasarle todos los argumentos posicionales y de clave que se desee.
Utilizamos *args
y
**kwargs
en la función interna, pero
también le tenemos que pasar eso mismo a la función
decorada (funcion_externa
):
def decoradora(funcion_externa):
def funcion_interna(*args, **kwargs):
print("Antes...")
funcion_externa(*args, **kwargs)
print("Después...")
return funcion_interna
96. Modifica la función de saludo y dale los siguientes parámetros:
Nombre
Apellidos
Edad
Ciudad
Dentro de la función de saludo, tendrás que sustituir la frase inicial (la del ejercicio 92), para que imprima esta frase con los valores de los argumentos:
Soy {nombre} {apellidos}. Tengo {edad} años, y vivo en {ciudad}.
Haz una llamada a la función saludo()
, con un
nombre
y apellidos
cualquiera
como argumentos posicionales, y edad
y
ciudad
con argumentos de clave.
Todo el código quedará así. Fíjate en las partes marcadas en el código, que son las que he modificado para este ejercicio:
def decoradora(funcion_externa):
def funcion_interna(*args,**kwargs):
print("Antes...")
funcion_externa(*args,**kwargs)
print("Después...")
return funcion_interna
@decoradora
def saludo(nombre, apellidos, edad, ciudad):
print(f"Soy {nombre} {apellidos}. Tengo {edad} años, y vivo en {ciudad}.")
saludo("Amaya",
"Vallejo Palacios",
edad=28,
ciudad="Oporto"
)
Espacio publicitario