trubidata
Lección 7 de 12·24 min

Funciones

En esta guía (17)

Hasta ahora usaste funciones que trae Python (print, len, sorted). En esta lección vas a escribir las tuyas. Una función es un bloque de código con nombre que recibe datos, hace algo y devuelve un resultado. Es la herramienta principal para no repetir código, dividir un problema en partes chicas y poder probar cada parte por separado.

Definir y llamar una función

python
def saludar(nombre):
    print(f"Hola, {nombre}")

saludar("Ana")
saludar("Bruno")
text
Hola, Ana
Hola, Bruno
  • def empieza la definición; después va el nombre de la función (en snake_case), los parámetros entre paréntesis y dos puntos.
  • El cuerpo va indentado, como en un if.
  • Definir la función no la ejecuta. Se ejecuta cuando la llamás: saludar("Ana").
  • nombre es un parámetro: una variable que recibe el valor que pasás al llamar. Ese valor ("Ana") se llama argumento.

La función tiene que estar definida antes de llamarla: Python lee el archivo de arriba hacia abajo.

return: devolver un resultado

La mayoría de las funciones útiles calculan algo y lo devuelven con return:

python
def precio_final(precio, descuento):
    return precio - precio * descuento / 100

total = precio_final(2000, 15)
print(total)                     # 1700.0
print(precio_final(500, 10) * 2) # 900.0

return hace dos cosas: termina la función y entrega el valor a quien la llamó. Ese valor lo podés guardar en una variable, imprimir o usar en otra cuenta.

Es la confusión más común al empezar:

python
def doble_con_print(n):
    print(n * 2)

def doble_con_return(n):
    return n * 2

a = doble_con_print(5)    # muestra 10, pero...
b = doble_con_return(5)   # no muestra nada, pero...
print(a)                  # None
print(b)                  # 10
text
10
None
10

print muestra algo en pantalla y nada más. return devuelve un valor que el resto del programa puede usar. Una función sin return devuelve None. Regla práctica: las funciones que calculan devuelven; el print lo hace quien llama, cuando quiere mostrar el resultado.

Varios return

Una función puede tener varios return; el primero que se ejecuta la termina:

python
def signo(n):
    if n > 0:
        return "positivo"
    if n < 0:
        return "negativo"
    return "cero"

print(signo(-4))  # negativo
print(signo(0))   # cero

No hace falta else después de un return: si la ejecución llegó a la línea siguiente, es porque la condición no se cumplió.

Parámetros con valor por defecto

Un parámetro puede tener un valor por defecto, que se usa si no le pasás ese argumento:

python
def formatear_precio(monto, moneda="$", decimales=2):
    return f"{moneda} {monto:,.{decimales}f}"

print(formatear_precio(1234.5))               # $ 1,234.50
print(formatear_precio(1234.5, "US$"))        # US$ 1,234.50
print(formatear_precio(1234.5, decimales=0))  # $ 1,234

Los parámetros con valor por defecto van después de los obligatorios.

Argumentos por posición y por nombre

Al llamar, podés pasar los argumentos por posición (en el orden de la definición) o por nombre:

python
def crear_usuario(nombre, edad, ciudad):
    return f"{nombre} ({edad}), {ciudad}"

print(crear_usuario("Ana", 28, "Salta"))
print(crear_usuario(ciudad="Salta", nombre="Ana", edad=28))
print(crear_usuario("Ana", ciudad="Salta", edad=28))
text
Ana (28), Salta
Ana (28), Salta
Ana (28), Salta

Pasar por nombre hace la llamada más clara cuando hay varios parámetros, sobre todo booleanos: exportar(datos, comprimir=True) se entiende mejor que exportar(datos, True). Si mezclás, los posicionales van primero.

Devolver varios valores

Una función puede devolver varios valores separándolos con comas. En realidad devuelve una tupla, que se desempaqueta al recibirla (lección 5):

python
def estadisticas(numeros):
    minimo = min(numeros)
    maximo = max(numeros)
    promedio = sum(numeros) / len(numeros)
    return minimo, maximo, promedio

mn, mx, prom = estadisticas([4, 8, 15, 16, 23, 42])
print(f"min={mn} max={mx} promedio={prom:.2f}")
text
min=4 max=42 promedio=18.00

*args y **kwargs (introducción)

A veces querés una función que acepte cualquier cantidad de argumentos. *args los junta en una tupla:

python
def sumar_todo(*numeros):
    total = 0
    for n in numeros:
        total += n
    return total

print(sumar_todo(1, 2))           # 3
print(sumar_todo(5, 10, 15, 20))  # 50
print(sumar_todo())               # 0

Y **kwargs junta los argumentos por nombre en un diccionario:

python
def describir(**datos):
    for clave, valor in datos.items():
        print(f"{clave}: {valor}")

describir(nombre="Ana", rol="analista", activa=True)
text
nombre: Ana
rol: analista
activa: True

Lo que importa es el * y el **; los nombres args y kwargs son solo una convención. No vas a escribirlos todos los días, pero conviene reconocerlos porque aparecen mucho en código ajeno. print mismo acepta cualquier cantidad de argumentos de esta forma.

Alcance: variables locales y globales

Las variables que creás dentro de una función son locales: existen solo mientras la función se ejecuta y no se ven desde afuera.

python
def calcular():
    resultado = 42
    return resultado

calcular()
print(resultado)
text
NameError: name 'resultado' is not defined

Las variables definidas fuera de cualquier función son globales. Una función las puede leer:

python
IVA = 0.21

def con_iva(precio):
    return precio * (1 + IVA)

print(con_iva(1000))  # 1210.0

Pero si asignás a ese nombre adentro de la función, Python crea una variable local nueva con el mismo nombre, y la global no cambia:

python
contador = 0

def incrementar():
    contador = 1   # esta es una variable LOCAL, distinta de la global
    return contador

incrementar()
print(contador)  # 0

Existe la palabra global para modificar una global desde adentro, pero evitala: hace que el resultado de una función dependa de cosas que no se ven en su llamada y vuelve el código difícil de seguir. La alternativa es simple: pasá el valor como parámetro y devolvé el resultado.

python
def incrementar(contador):
    return contador + 1

contador = 0
contador = incrementar(contador)
print(contador)  # 1

Por convención, las constantes globales (valores que no cambian, como IVA) se escriben en MAYÚSCULAS.

Funciones puras

Una función pura cumple dos condiciones:

  1. Su resultado depende solo de sus parámetros.
  2. No modifica nada afuera: no cambia globales, no modifica las listas que recibe, no imprime.
python
# Impura: modifica la lista que recibe
def agregar_iva_impura(precios):
    for i in range(len(precios)):
        precios[i] = precios[i] * 1.21

# Pura: devuelve una lista nueva y deja la original intacta
def agregar_iva(precios):
    return [p * 1.21 for p in precios]

originales = [100, 200]
nuevos = agregar_iva(originales)
print(originales)  # [100, 200]
print(nuevos)      # [121.0, 242.0]

Las funciones puras son más fáciles de entender, de reusar y de probar: con los mismos argumentos, siempre dan el mismo resultado. No todo puede ser puro (en algún momento hay que imprimir o guardar un archivo), pero conviene que la mayor parte de la lógica lo sea y dejar la entrada y salida en pocas funciones.

Cuidado con los valores por defecto mutables

Un valor por defecto se crea una sola vez, cuando se define la función. Si es una lista, se comparte entre llamadas:

python
def agregar_tarea(tarea, lista=[]):   # ¡trampa!
    lista.append(tarea)
    return lista

print(agregar_tarea("lavar"))     # ['lavar']
print(agregar_tarea("cocinar"))   # ['lavar', 'cocinar']  (¿?)

La solución estándar es usar None como valor por defecto y crear la lista adentro:

python
def agregar_tarea(tarea, lista=None):
    if lista is None:
        lista = []
    lista.append(tarea)
    return lista

print(agregar_tarea("lavar"))     # ['lavar']
print(agregar_tarea("cocinar"))   # ['cocinar']

Type hints

Los type hints (anotaciones de tipo) indican qué tipo esperan los parámetros y qué tipo devuelve la función:

python
def precio_final(precio: float, descuento: float = 0) -> float:
    return precio - precio * descuento / 100

def iniciales(nombres: list[str]) -> str:
    return "".join(n[0].upper() for n in nombres)

def contar_palabras(texto: str) -> dict[str, int]:
    conteo: dict[str, int] = {}
    for palabra in texto.lower().split():
        conteo[palabra] = conteo.get(palabra, 0) + 1
    return conteo

print(precio_final(1000, 10))                  # 900.0
print(iniciales(["ana", "bruno", "carla"]))    # ABC
print(contar_palabras("sol y luna y sol"))     # {'sol': 2, 'y': 2, 'luna': 1}
  • nombre: tipo anota un parámetro; -> tipo anota lo que devuelve.
  • list[str] es "lista de strings"; dict[str, int], "diccionario de claves string y valores int".
  • Para "puede ser un número o None" se escribe float | None.

Python no verifica los type hints al ejecutar: son documentación. Pero tu editor sí los usa para avisarte errores y autocompletar mejor, y hay herramientas (como mypy) que los chequean. Desde acá en adelante el curso los usa en las funciones.

Docstrings

Un docstring es un string entre triples comillas en la primera línea de la función. Explica qué hace, sin repetir el código:

python
def promedio(numeros: list[float]) -> float:
    """Devuelve el promedio de una lista de números.

    Si la lista está vacía, devuelve 0.
    """
    if not numeros:
        return 0
    return sum(numeros) / len(numeros)

print(promedio([7, 8, 10]))  # 8.333333333333334
print(promedio([]))          # 0

help(promedio) en el REPL muestra el docstring, y tu editor lo muestra cuando pasás el mouse por encima de la función. Una línea alcanza para funciones simples; para más complejas, agregá qué recibe, qué devuelve y en qué casos.

Errores comunes

  • Usar print en vez de return. Si después necesitás el resultado, la función te da None. Calculá y devolvé; mostrá afuera.
  • Olvidar los paréntesis al llamar. saludar (sin paréntesis) no ejecuta nada: es la función misma. Para llamarla, saludar().
  • Llamar con la cantidad equivocada de argumentos. Da TypeError: saludar() missing 1 required positional argument: 'nombre'.
  • Querer usar una variable local afuera de su función. Da NameError. Devolvé el valor con return.
  • Modificar globales desde funciones. Funciona con global, pero complica todo. Pasá parámetros y devolvé resultados.
  • Lista o diccionario como valor por defecto. Se comparte entre llamadas. Usá None y creá la colección adentro.
  • Funciones que hacen demasiado. Si te cuesta ponerle nombre porque hace "esto y aquello y lo otro", partila en varias.

Practicá

  1. Escribí es_bisiesto(anio: int) -> bool. Un año es bisiesto si es divisible por 4, salvo que sea divisible por 100; pero los divisibles por 400 sí son bisiestos. Probala con 2024, 1900, 2000 y 2026.

  2. Escribí resumen(numeros: list[float]) -> tuple[float, float, float] que devuelva el mínimo, el máximo y el promedio, y que devuelva (0, 0, 0) si la lista está vacía. Recibí el resultado desempaquetando.

  3. Este código funciona, pero usa una global y modifica la lista. Reescribilo con una función pura que reciba la lista de precios y el porcentaje, y devuelva una lista nueva:

    python
    precios = [100, 250, 80]
    aumento = 10
    
    def aplicar_aumento():
        global precios
        precios = [p + p * aumento / 100 for p in precios]
    
    aplicar_aumento()
    print(precios)
    
  4. Escribí validar_password(clave: str) -> list[str] que devuelva la lista de problemas encontrados (vacía si es válida): menos de 8 caracteres, sin ningún número, sin ninguna mayúscula. Pista: any(c.isdigit() for c in clave) es True si al menos un carácter es dígito.

Soluciones

Ejercicio 1:

python
def es_bisiesto(anio: int) -> bool:
    """Indica si un año es bisiesto en el calendario gregoriano."""
    return anio % 4 == 0 and (anio % 100 != 0 or anio % 400 == 0)

for anio in [2024, 1900, 2000, 2026]:
    print(anio, es_bisiesto(anio))
text
2024 True
1900 False
2000 True
2026 False

Una función que devuelve un booleano muchas veces puede devolver la condición directamente, sin if.

Ejercicio 2:

python
def resumen(numeros: list[float]) -> tuple[float, float, float]:
    """Devuelve (mínimo, máximo, promedio); (0, 0, 0) si la lista está vacía."""
    if not numeros:
        return 0, 0, 0
    return min(numeros), max(numeros), sum(numeros) / len(numeros)

minimo, maximo, prom = resumen([3.5, 9, 6.5])
print(minimo, maximo, prom)  # 3.5 9 6.333333333333333
print(resumen([]))           # (0, 0, 0)

Ejercicio 3:

python
def aplicar_aumento(precios: list[float], porcentaje: float) -> list[float]:
    """Devuelve una lista nueva con los precios aumentados en el porcentaje dado."""
    return [p + p * porcentaje / 100 for p in precios]

precios = [100, 250, 80]
nuevos = aplicar_aumento(precios, 10)
print(precios)  # [100, 250, 80]
print(nuevos)   # [110.0, 275.0, 88.0]

Ahora la función no depende de nada que no reciba, no cambia la lista original y la podés usar con cualquier lista y porcentaje.

Ejercicio 4:

python
def validar_password(clave: str) -> list[str]:
    """Devuelve la lista de problemas de la clave; vacía si es válida."""
    problemas = []
    if len(clave) < 8:
        problemas.append("tiene menos de 8 caracteres")
    if not any(c.isdigit() for c in clave):
        problemas.append("no tiene números")
    if not any(c.isupper() for c in clave):
        problemas.append("no tiene mayúsculas")
    return problemas

print(validar_password("hola"))
print(validar_password("Clave2026"))
text
['tiene menos de 8 caracteres', 'no tiene números', 'no tiene mayúsculas']
[]

Devolver la lista de problemas (en vez de solo True o False) permite que quien llama muestre mensajes útiles.

Resumen

  • def nombre(parametros): define una función; se ejecuta al llamarla con nombre(argumentos).
  • return termina la función y devuelve un valor; sin return, devuelve None. print no es return.
  • Los parámetros pueden tener valor por defecto, y los argumentos se pueden pasar por posición o por nombre.
  • return a, b devuelve una tupla que se desempaqueta al recibirla.
  • *args junta argumentos sueltos en una tupla; **kwargs, argumentos con nombre en un diccionario.
  • Las variables de una función son locales; evitá global y preferí funciones puras.
  • Nunca uses una lista o un diccionario como valor por defecto: usá None.
  • Los type hints (precio: float -> float) y los docstrings documentan la función para personas y herramientas.

← Volver al temario