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],)
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'>}
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.