3.2. Funciones#

El concepto de función es heredado de las matemáticas, representan una operación y en su sentido más puro es un bloque de código que toma una serie de entradas inputs y genera una salida.

En programación las funciones nos ayudan a escribir menos código, hacer que el código sea más legible y esté mejor organizado y simplifican nuestras operaciones pero su principal función es clave, son BLOQUES DE CÓDIGO REUTILIZABLES.

Retomemos el ejemplo de la lección anterior con el índice de masa corporal, en el tema de control de flujo calculamos el índice de masa corporal, pero como lo vimos en los bucles, había que escribir la operación una y otra vez cuando queríamos obtener el valor.

Esta es la fórmula:

\(IMC = \frac{peso}{talla^2}\)

Una función matemática se vería así:

\(IMC(peso; talla) = \frac{peso}{talla^2}\)

Es decir, la función IMC toma dos parámetros, peso y talla y regresa el resultado de dividir el peso por el cuadrado de la talla.

En python, podemos crear una función a partir de ello y se vería así:

def calcular_imc(peso, talla):
    return peso / talla**2

Esta función puede ser utilizada múltiples veces de la siguiente manera:

for paciente in lista_de_pacientes:
    imc = calcular_imc(paciente['peso'], paciente['talla'])
    print('Paciente', paciente['ID'], 'Tiene imc de', imc)

Desglosemos qué está pasando.

3.2.1. Sintaxis#

En python definimos una función de la siguiente forma:

def suma(a, b):
    "Esta función suma los argumentos a y b y devuelve el resultado"
    return a + b
  1. El encabezado comienza siempre con la palabra clave def.

  2. Inmediatamente después del encabezado de la función, se puede poner un string (str) cuya función es explicar qué hace y cómo se usa la función, se le llama Docstring.

  3. La siguiente palabra es el nombre que recibirá la función.

  4. Luego se abren paréntesis.

  5. Dentro de los paréntesis puede ir lo sigueinte:

    • Nada: ().

    • Parámetros posicionales (positional arguments o más comumente args).

    • Parámetros de palabra clave (Key word arguments, o más comunmente kwargs o kws).

    • Cualquier combinación de estos, pero siempre van primero los posicionales y luego los kws.

  6. Dos puntos : para marcar el comienzo del cuerpo de la función.

  7. Cuerpo de la función en el siguiente nivel de sangría.

3.2.1.1. Parámetros#

Importante

Diferencia entre «parámetro» y «argumento».

Los parámetros se definen en el encabezado de la función. def foo(x): define un parámetro x para la función foo.

Los argumentos son los valores que se pasan a la función para cubrir sus parámetros, por ejemplo:

def imc(peso, talla):
    return peso / talla ** 2

p = 77
t = 1.72
print(imc(p, t))

En esta función, los parámetros son peso y talla en la función de imc. Los argumentos al llamar la función son 77 y 1.72, contenidos en las variables llamadas p y t.

Uno de los principales poderes de las funciones es aceptar argumentos para realizar su trabajo. Veamos el ejemplo más sencillo, una función matemática:

\(f(x) = x^2\)

En python esta función se ve así:

def f(x): 
    return x**2

Esta función toma un solo argumento, x y devuelve su valor elevado al cuadrado con la palabra clave return.

Nota

Si una función no tiene explícitamente la palabra return en realidad regresa un tipo nulo NoneType. Es decir, que las siguientes dos funciones son equivalentes:

def saludar(nombre):
    print('Hola', nombre)

# y

def saludar2(nombre):
    print('Hola', nombre)
    return None

Como se comentó previamente, existen básicamente dos tipos de argumentos, los posicionales y los de palabra clave, veamos cómo funcionan. Para las siguientes dos secciones usaremos el ejemplo de la siguiente función.

3.2.1.1.1. Parámetros posicionales#

Los parámetros posicionales se pasan a la función en el orden definido en la función. Por ejemplo:

def mas_grande(x, y):
    "Esta función muestra los valores de X y Y"
    print(f'{x=}, {y=}')

# Podríamos utlizar la función de cualquiera de las siguientes formas:

x = 7
y = 6
mas_grande(x, y)
mas_grande(y, x)    
mas_grande(7, 6)    
mas_grande(x=y, y=x) # intercambiados
x=7, y=6
x=6, y=7
x=7, y=6
x=6, y=7

Nota

El str está formateado con f-string, revisa el tema correspondiente para ver cómo funciona.

En los primeros dos usos de la función, pasamos los argumentos en forma posicional, en el tercero lo hicimos en forma de palabras clave kws, esto último lo veremos en la siguiente sección.

Esto es porque la función no nos ha limitado en cómo utilizarla, es decir, podemos ejecutarla como queramos, pero nota que cuando usamos la función con argumentos posicionales, el orden en que pasamos los argumentos cambia como responde la función.

Aunque la variable, fuera de la función, se llame como alguno de los parámetros, dentro de la función los valores asociados a las variables se asignan al nombre del parámetro de la función. Observa el segundo ejemplo, mas_grande(y, x) aunque las variables se llamen x y y, dentro de la función se transforman, porque el valor de la variable x, se asigna al parámetro y y viceversa.

Es posible hacer que los argumentos de una función sean obligatoriamente posicionales.

def mas_grande_pos(x, y, /):
    "Esta función muestra los valores de X y Y"
    print(f'{x=}, {y=}')

mas_grande_pos(7, 6) # Sí funciona
mas_grande_pos(7, y=6) # No funciona
x=7, y=6
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[2], line 6
      3     print(f'{x=}, {y=}')
      5 mas_grande_pos(7, 6) # Sí funciona
----> 6 mas_grande_pos(7, y=6) # No funciona

TypeError: mas_grande_pos() got some positional-only arguments passed as keyword arguments: 'y'

Como ves en este ejemplo, el error ocurre porque ambos parámetros, x y y son obligatoriamente posicionales, es la diagonal / en el encabezado de la función lo que señaliza a los argumentos posicionales obligatorios. Es posible tener casos mixtos, por ejemplo, en la siguiente función:

def mas_grande_pos_2(x, /, y): # la / está ahora a la mitad
    "Esta función muestra los valores de X y Y"
    print(f'{x=}, {y=}')

mas_grande_pos_2(7, 6) # Sí funciona
mas_grande_pos_2(7, y=6) # También funciona
mas_grande_pos_2(x=7, y=6) # No funciona
x=7, y=6
x=7, y=6
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 7
      5 mas_grande_pos_2(7, 6) # Sí funciona
      6 mas_grande_pos_2(7, y=6) # También funciona
----> 7 mas_grande_pos_2(x=7, y=6) # No funciona

TypeError: mas_grande_pos_2() got some positional-only arguments passed as keyword arguments: 'x'
3.2.1.1.1.1. Ejercicio personal#

¿Por qué falló la última ejecución?

3.2.1.2. Parámetros de palabra clave kws#

Los parámetros de «palabra clave», en adelante ksw, se utilizan en la función de forma explícita con el operador de asignación = y el nombre del parámetro.

Tomemos el siguiente ejemplo

3.2.1.3. Parámetros de palabra clave (kwargs, o kws)#

Los argumentos de palabra clave permiten especificar a qué parámetro corresponde cada valor usando nombre=valor. Son útiles cuando hay muchos parámetros o cuando queremos cambiar solo uno sin importar el orden.

def saludar(nombre, saludo):
    print(f"{saludo}, {nombre}!")

saludar(nombre="Chris", saludo="Buenos días") # imprime Buenos días Chris
saludar(saludo="Buenos días", nombre="Chris") # imprime Buenos días Chris también

Observa que ambos argumentos se están pasando por palabra clave, pero podrían de igualforma pasarse como argumentos posicionales.

saludar("Chris", "Buenos días")

Para hacer obligatorios los kws se utiliza un asterizco antes:

def saludar(*, nombre, saludo):
    print(f"{saludo}, {nombre}!")

3.2.1.4. Valores por defecto#

Al definir una función, podemos asignar valores por defecto a los parámetros. Estos se usan si no se pasan argumentos para el parámetro en cuestión cuando se llama la función

Por ejemplo:

def potencia(base, exponente=2):
    return base ** exponente

potencia(3)      # 9
potencia(3, 3)   # 27

Nota que aunque la sintaxis de los parámetros con valor por defecto es parecida a como se pasan argumentos tipo kws, pero no te confundas, los parámetros con valores por defecto pueden ser args o kws.

3.2.2. Combinado todo#

Definamos una función con todos los tipos de parámetros comentados y lo aprendido hasta el momento. Analiza la siguiente celda de código y luego resuelve los ejercicios de la siguiente sección.

def grado_imc(imc):
    """
    Clasifica el grado del Índice de Masa Corporal (IMC) según rangos definidos.

    Utiliza el valor del IMC para determinar la categoría correspondiente:
    - IMC < 20: 'Peso bajo'
    - 20 <= IMC < 27: 'Peso normal'
    - 27 <= IMC < 30: 'Sobrepeso'
    - IMC >= 30: 'Obeso' (valor por defecto)

    Parámetros
    ----------
    imc : float
        Valor del Índice de Masa Corporal a clasificar.

    Retorna
    -------
    str
        Cadena con la categoría correspondiente al IMC:
        'Peso bajo', 'Peso normal', 'Sobrepeso' u 'Obeso'.
    """
    grado = 'Obeso'
    
    if imc < 20:
        grado = 'Peso bajo'
    elif 20 <= imc < 27:
        grado = 'Peso normal'
    elif 27 <= imc < 30:
        grado = 'Sobrepeso'

    return grado


def calcular_imc( # observa que el encabezado non tiene que estar en una sola línea
                  # esto ayuda a que sea más legible
    peso,         # posicional obligado
    talla,        # posicional obligado 
    /,            # terminando los posicionales obligados
    calcular_grado=False, # puede ser posicional o por palabra clave
    *, # a partir de aquí los demás parámetros deben ser de palabra clave
    generar_reporte=False 
):
    """
    Calcula el Índice de Masa Corporal (IMC) a partir del peso y la talla.

    El cálculo se realiza como `peso / talla**2`. Opcionalmente, puede determinar 
    el grado del IMC (por ejemplo, normopeso, sobrepeso, obesidad) utilizando una 
    función externa llamada `grado_imc`, y generar un reporte impreso.

    Parámetros
    ----------
    peso : float
        Peso del individuo en kilogramos. Obligatorio y posicional.
    talla : float
        Talla del individuo en metros. Obligatorio y posicional.
    calcular_grado : bool, opcional
        Si es `True`, también se evalúa y retorna el grado del IMC usando `grado_imc()`.
        Por defecto es `False`.
    generar_reporte : bool, palabra clave
        Si es `True`, imprime un reporte con el IMC y, si se solicita, el grado.
        Por defecto es `True`.

    Retorna
    -------
    float o tuple
        - Si `calcular_grado` es `False`, retorna solo el valor del IMC (`float`).
        - Si `calcular_grado` es `True`, retorna una tupla (`imc`, `grado`).

    """
    imc = peso / talla ** 2
    grado = grado_imc(imc) if calcular_grado is True else None

    if generar_reporte is True:
        reporte = f'Para {peso=} y {talla=}, el imc es {imc}'
        if grado is not None:
            reporte += f' y el grado es: {grado}'
        print(reporte)
    
    return (imc, grado) if calcular_grado else (imc)



imc = calcular_imc(92, 1.77) # Guarda el IMC en una variable
print(imc)
29.365763350250564

3.2.2.1. Ejercicios#

  1. Analiza el código con detenimiento

  2. Lee con detenimiento los docstrings

  3. Contesta: ¿por qué en la primera función iniciamos con grado = 'Obeso'?

  4. Utiliza el docstring de la función calcular_imc para cambiar su forma de actuar.

  5. Genera un diagrama de flujo que explique cómo funciona, utiliza draw.io o la herramienta que prefieraas.

3.2.3. Funciones con parámetros indefinidos.#

Existen algunos casos particulares en donde las funciones pueden aceptar cualquier cantidad de argumentos, la siguiente función es un ejemplo mínimo.

def foo(*args, **kwargs):
    print(args)
    print(kwargs)

print(foo(1, 2, 3, 4, 5, a=123, nombre='Pedro'))
(1, 2, 3, 4, 5)
{'a': 123, 'nombre': 'Pedro'}
None

Si analizamos al función nos damos cuenta que cuando ponemos un asterisco al inicio parámetro args acepta cualquier cantidad de argumentos posicionales, cuyos valores, se concentran en una lista. Por otro lado, al poner dos asteriscos kwargs acepta cualquier cantidad de argumentos de palabra clave y los guarda en un diccionario, donde las llaves son las palabras clave y los valores son los valores que se pasaron.

Es posible de hecho, pasar args y kwargs listas (o tuplas) y diccionarios como argumentos.

mis_args = [1, 7, 42]
mis_kwargs = {'nombre': 'Chris', 'apellido': 'Delaflor'}

foo(*mis_args, **mis_kwargs)
(1, 7, 42)
{'nombre': 'Chris', 'apellido': 'Delaflor'}

Nota

Si bien en este curso no construiremos funciones con args o kwargs de este tipo, sí utilizaremos muchas que los implementan, por lo que es importante que los conozcas y sepas cómo funcionan.

3.2.4. Ejercicio personal#

Realiza este ejercicio si te sientes valiente, verás la solución al principio de la siguiente lección.

Elabora una función que calcule la media de un conjunto de datos.