Funciones

Las funciones de Python admiten más formas de recibir argumentos que casi cualquier otro lenguaje. Esta lección las recorre todas, y se detiene en el error clásico del valor por omisión mutable, que cometen también los que llevan años.

Todas las formas de pasar argumentos

def pedido(producto, cantidad=1, *extras, urgente=False, **opciones):
    ...

pedido("mochila")
pedido("mochila", 2)
pedido("mochila", 2, "correa", "funda")
pedido("mochila", urgente=True, color="azul", envoltorio=True)
salida1 x mochila
2 x mochila
2 x mochila + correa, funda
1 x mochila  URGENTE  {'color': 'azul', 'envoltorio': True}

Esa firma tiene las cinco cosas que se pueden declarar, en el único orden posible:

Parte Qué recoge
producto obligatorio
cantidad=1 opcional, con valor por omisión
*extras los demás posicionales, en una tupla
urgente=False tras el *, solo se puede pasar con nombre
**opciones los demás con nombre, en un diccionario

Forzar la forma de llamar

def dividir(a, b, /, *, redondear=False):
    ...

dividir(7, 2)                     # bien
dividir(7, 2, redondear=True)     # bien
dividir(a=7, b=2)                 # error
salidaTypeError: dividir() got some positional-only arguments passed as keyword arguments: 'a, b'

La barra / marca lo que va solo por posición, y el asterisco * lo que va solo con nombre. Sirve para dos cosas: que nadie dependa de los nombres de tus parámetros —y así poder cambiarlos sin romper nada—, y obligar a que las opciones se escriban con nombre, que se lee muchísimo mejor que dividir(7, 2, True).

La trampa del valor por omisión mutable

def mal(x, destino=[]):
    destino.append(x)
    return destino

mal(1); mal(2); mal(3)
salidamal : [1] [1, 2] [1, 2, 3]

La lista no se vacía entre llamadas: se va acumulando. Y el porqué se ve de un vistazo:

salidamal.__defaults__ = ([1, 2, 3],)
El valor por omisión se evalúa UNA vez, al definir la función, no en cada llamada. Esa lista es un único objeto que vive pegado a la función para siempre. Es el error clásico de Python, y lo cometen también los que llevan años.

La solución es siempre la misma:

def bien(x, destino=None):
    if destino is None:
        destino = []
    destino.append(x)
    return destino
salidabien: [1] [2] [3]

La regla: nunca pongas una lista, un diccionario o un conjunto como valor por omisión. Con números, cadenas y None no hay problema, porque no se pueden modificar.

Devolver varias cosas

def dividir_entero(a, b):
    return a // b, a % b

coc, resto = dividir_entero(17, 5)
salidacociente 3, resto 2   (en realidad devuelve la tupla (3, 2))

No es una característica especial: devuelve una tupla y tú la desempaquetas. Pero se usa tanto que parece sintaxis propia.

Anotaciones de tipo

def area(base: float, altura: float) -> float:
    return base * altura / 2

area("ab", 2)
salidaarea(3, 4) = 6.0
area('ab', 2) = TypeError: unsupported operand type(s) for /: 'str' and 'int'
las anotaciones quedan en: {'base': <class 'float'>, 'altura': <class 'float'>, 'return': <class 'float'>}
Las anotaciones no se comprueban al ejecutar. Python no miró el float y dejó pasar la cadena; el error llegó después, y fíjate en que llegó en la división, no en la multiplicación, porque "ab" * 2 es perfectamente válido. Las anotaciones son documentación que el editor entiende, y las comprueba una herramienta aparte —mypy, ruff o ty— que se ejecuta antes, no durante.

Aun así, ponlas. En un proyecto de más de un archivo se pagan solas: el editor empieza a autocompletar de verdad y a avisarte de errores mientras escribes.

Las funciones son objetos

ops = {"suma": lambda a, b: a + b, "resta": lambda a, b: a - b}
ops["suma"](2, 3)

sorted(["bbb", "a", "cc"], key=len)
salidaops['suma'](2,3) = 5
sorted por clave: ['a', 'cc', 'bbb']

Una lambda es una función de una sola expresión, sin nombre. Son útiles justo donde se usan ahí: como argumento de otra función. Si necesitas más de una línea, o vas a reutilizarla, escribe un def normal: tiene nombre, admite documentación y aparece mejor en los errores.

Quieres… Escribes
Un parámetro opcional def f(x, y=0)
Un número variable de argumentos *args
Opciones con nombre **kwargs
Obligar a usar el nombre un * antes
Un valor por omisión mutable =None y crearlo dentro
Devolver dos cosas return a, b
Documentar los tipos anotaciones, y mypy o ruff aparte

Todo el código de esta lección se ejecutó con Python 3.14 en un contenedor limpio antes de publicarla; las salidas y los errores están copiados de esa ejecución.