Saltar al contenido
🐍 Curso de Python
/

Módulo 1 · Lección 35 de 59

Argumentos por defecto

Cómo dar un valor previo a un parámetro para simplificar las llamadas, y la trampa del valor por defecto mutable.

⏱ 11 min de lectura

Cuando una función tiene un parámetro que casi siempre vale lo mismo, repetirlo en cada llamada es ruido. El IVA general español es del 21 % en la inmensa mayoría de los casos; escribir 0.21 cincuenta veces en el programa es exactamente el tipo de repetición que las funciones venían a eliminar.

Python permite dar a un parámetro un valor previo, que se usará cuando quien llama no diga otra cosa.

Un valor por defecto#

Se escribe con un = en la propia definición:

python
def calcular_precio_final(precio, iva=0.21):
        return precio * (1 + iva)


print(f"{calcular_precio_final(100):.2f}")
print(f"{calcular_precio_final(100, 0.10):.2f}")
print(f"{calcular_precio_final(100, iva=0.04):.2f}")
Salida en pantalla
121.00
110.00
104.00

En la primera llamada no pasamos el segundo argumento, así que iva vale 0.21. En las otras dos lo pasamos y el valor por defecto se ignora. Un parámetro con valor por defecto es opcional; uno sin valor por defecto es obligatorio.

Fíjate en que ya conocías esto sin saberlo: print(a, b, sep=", "), sorted(lista, reverse=True) o round(numero, 2) son funciones con parámetros opcionales. Cuando llamas a print sin sep, se usa su valor por defecto, que es un espacio.

Los opcionales van después de los obligatorios#

Esta es la única regla de sintaxis que hay que recordar, y Python la impone sin margen:

python
def calcular_precio_final(iva=0.21, precio):
        return precio * (1 + iva)
Salida en pantalla
  File "/Users/erwin/curso-python/precios.py", line 1
    def calcular_precio_final(iva=0.21, precio):
                                        ^^^^^^
SyntaxError: parameter without a default follows parameter with a default

La razón es la misma que con los argumentos por nombre en la llamada. Si los opcionales pudieran ir primero, una llamada como calcular_precio_final(100) sería ambigua: ¿ese 100 es el IVA o el precio? Poniendo los obligatorios delante, los primeros argumentos siempre corresponden a los primeros parámetros y no hay nada que adivinar.

Llamadas cortas para el caso normal#

El verdadero valor de esto aparece cuando una función tiene muchas opciones. Puedes ofrecer una función muy configurable sin que el uso habitual sea incómodo:

python
def crear_ticket(producto, cantidad=1, moneda="euros", regalo=False):
        linea = f"{cantidad} x {producto} ({moneda})"
    if regalo:
                linea += " [para regalo]"
    return linea


print(crear_ticket("Camiseta"))
print(crear_ticket("Camiseta", 3))
print(crear_ticket("Camiseta", regalo=True))
print(crear_ticket("Camiseta", 2, "libras", True))
Salida en pantalla
1 x Camiseta (euros)
3 x Camiseta (euros)
1 x Camiseta (euros) [para regalo]
2 x Camiseta (libras) [para regalo]

La tercera llamada es la interesante: queremos cambiar regalo, que es el cuarto parámetro, sin tocar el segundo ni el tercero. Por posición sería imposible; por nombre es inmediato. Los valores por defecto y los argumentos por nombre son un equipo: en cuanto hay más de dos opcionales, se usan por nombre casi siempre.

La trampa del valor por defecto mutable#

Y ahora el comportamiento de Python que más desconcierta a quien lleva pocos meses. Esta función añade un elemento a una lista y, si no le das ninguna, empieza con una vacía. Antes de mirar la salida, piensa qué debería imprimir: lo esperable sería ['pan'], ['leche'] y ['huevos'], tres listas nuevas.

python
def añadir(elemento, lista=[]):
        lista.append(elemento)
    return lista


print(añadir("pan"))
print(añadir("leche"))
print(añadir("huevos"))
Salida en pantalla
['pan']
['pan', 'leche']
['pan', 'leche', 'huevos']

La lista se va acumulando entre llamadas distintas, como si tuviera memoria.

No es una teoría: el valor por defecto está guardado dentro de la propia función y se puede mirar.

python
def añadir(elemento, lista=[]):
        lista.append(elemento)
    return lista


print(añadir.__defaults__)
añadir("pan")
print(añadir.__defaults__)
Salida en pantalla
([],)
(['pan'],)

El valor por defecto de la función ha cambiado. La próxima llamada que no pase lista ya no empezará vacía.

La solución: None#

El patrón estándar, el que verás en todo el código profesional de Python, es usar None como valor por defecto y crear la lista dentro:

python
def añadir(elemento, lista=None):
        if lista is None:
                lista = []
    lista.append(elemento)
    return lista


print(añadir("pan"))
print(añadir("leche"))

compra = ["aceite"]
print(añadir("sal", compra))
Salida en pantalla
['pan']
['leche']
['aceite', 'sal']

Ahora sí. None es inmutable, así que no arrastra nada, y la línea lista = [] se ejecuta en cada llamada, creando una lista nueva cada vez. Cuando sí se pasa una lista, se usa esa.

En cuanto veas parametro=None seguido de un if parametro is None: en la primera línea de una función, ya sabes qué está pasando: es este patrón.

Qué se puede poner como valor por defecto#

Valor por defecto¿Seguro?Comentario
0, 3.14, -1SíLos números son inmutables
"euros", ""SíLas cadenas son inmutables
True, FalseSíLos booleanos son inmutables
NoneSíEs el comodín para todo lo demás
(1, 2)SíLas tuplas son inmutables
[], ["a"]NoLista mutable compartida entre llamadas
{}NoDiccionario mutable compartido entre llamadas
set()NoConjunto mutable compartido entre llamadas