Argumentos de función — nombres, valores por defecto y asteriscos
Pasar argumentos por nombre, valores por defecto y la regla sobre su orden, *args y **kwargs, y desempaquetar una lista o un diccionario en una llamada.
¿Cómo quieres aprender?
El problema que vamos a resolver
Las funciones del capítulo anterior solo se podían llamar de una forma: tantos argumentos como parámetros, en exactamente ese orden.
En la práctica no basta. Piensa en una función para un pedido:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3La segunda línea no produjo error — simplemente dio una respuesta sin sentido. Python no comprueba los tipos de los argumentos: aquí una cadena y un número cambiaron de posición y, como una f-string puede imprimir ambos, nada lo detuvo. Python no tiene forma de saber que el orden se invirtió.
Y si casi todos los pedidos son de un artículo? Tendrías que escribir 1 cada vez. Si una función tiene cuatro ajustes opcionales? Tendrías que pasarlos todos cada vez, incluidos los que no cambias.
Este capítulo es el resto de las formas de pasar argumentos: por nombre, con valores por defecto y en número desconocido.
Al terminar este capítulo podrás
- Pasar argumentos por nombre, y decir cuándo debes hacerlo
- Dar valores por defecto, y seguir la regla sobre su orden
- Hacer que algunos parámetros sean solo por palabra clave, para que solo se puedan pasar por nombre
- Tomar cualquier número de argumentos con
*args - Tomar argumentos de cualquier nombre con
**kwargs - Desempaquetar una lista o un diccionario en una llamada
Requisitos previos: Funciones — una decisión, un nombre.
Pasar por nombre
En lugar de recordar el orden, escribe el nombre:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penCon nombres, el orden deja de importar — quantity se escribió primero y aun así aterrizó en el sitio correcto.
¿Cuándo deberías? Cuando el argumento no se explica solo. order("pen", 3) se lee bien. Pero send(True, False) no te dice nada, mientras que send(retry=True, verbose=False) sí. Como regla: ponles nombre al pasar True/False o un número sin más contexto.
Valores por defecto
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penquantity=1 dice "asume uno si nadie dice otra cosa". El caso común se vuelve corto, y el caso raro sigue siendo expresable.
Una regla hay que obedecer: los parámetros con valores por defecto van siempre al final.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultLa razón queda clara si lo piensas un momento: dado order("pen"), ¿cómo sabría Python para qué parámetro es "pen"? Con los obligatorios primero, no hay ambigüedad.
Cualquier número de argumentos — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0El asterisco en *prices dice "reúne aquí los argumentos posicionales que sobran". No hace falta decidir de antemano cuántos vendrán, y que no venga ninguno está bien.
¿Qué es lo que se reúne?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')Una tupla — la tupla del capítulo catorce. La razón es elegante: cuántos argumentos hay se decide en el momento de la llamada y no debería cambiar después, así que un contenedor inmutable es lo adecuado.
El nombrepricesno es obligatorio. El nombre convencional es*args, que es lo que verás en la mayor parte del código. Pero un nombre significativo es mejor donde exista —*pricesdice a un lector qué está llegando,*argsno.
Argumentos de cualquier nombre — **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3Dos asteriscos reúnen los argumentos pasados por nombre, y el resultado es un diccionario — pares de nombre y valor, que es exactamente lo que debe ser.
Un asterisco da una tupla, dos dan un diccionario. Recuerda ese emparejamiento y lo demás se deduce.
Parámetros que solo se pueden pasar por nombre
Un parámetro que está después de *args no se puede pasar por posición, porque *args captura todos los argumentos posicionales restantes. Así que solo se puede pasar por nombre. Esto se llama un parámetro solo por palabra clave:
def total(*prices, discount=0.0):
return sum(prices) * (1 - discount)
print(total(100, 50))
print(total(100, 50, discount=0.1))
print(total(100, 50, 0.1))150.0
135.0
150.1Fíjate en la tercera línea. 0.1 no se convirtió en el descuento — entró en prices como un precio más, y la respuesta es errónea sin error. Para pasar discount tienes que escribir discount=0.1.
Puedes hacer un parámetro solo por palabra clave incluso cuando no quieres *args: pon un * solo en la lista de parámetros. Todo lo que venga después es solo por palabra clave:
def send(message, *, retry=False):
return f"{message} (retry={retry})"
print(send("hi", retry=True))
print(send("hi", True))hi (retry=True)
TypeError: send() takes 1 positional argument but 2 were given(Antes de la segunda línea Python también imprime líneas de rastreo; solo se muestra la última aquí.)
Esto es muy útil para ajustes True/False: el que llama tiene que escribir retry=True, así que cualquiera que lea la llamada ve para qué es el True.
El orden es fijo
Escritos juntos, la regla completa es:
- Primero los parámetros simples. Entre ellos, los que no tienen valor por defecto van antes que los que sí lo tienen.
- Luego
*args(o un*solo). - Luego los parámetros solo por palabra clave — con o sin valores por defecto, en cualquier orden.
- Al final,
**kwargs.
def f(a, b=1, *args, c, d=2, **kwargs):
print(a, b, args, c, d, kwargs)
f(10, c=3)
f(10, 20, 30, 40, c=3, e=5)10 1 () 3 2 {}
10 20 (30, 40) 3 2 {'e': 5}c no tiene valor por defecto, aun así va después de b=1, que sí lo tiene, y eso es legal — porque todo lo que viene después de *args llega por nombre, así que no hay duda de a dónde va cada valor. La regla de "los valores por defecto al final" solo se aplica a la parte antes de *args.
Parece excesivo al principio, y la razón es simple: Python tiene que saber qué argumento va dónde, y como *args toma "lo que sobre", ningún parámetro posicional puede seguirlo.
Desempaquetar en una llamada
Los asteriscos también se pueden usar en la llamada, donde hacen el trabajo contrario:
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))3 x pen
2 x bag*values desempaqueta la lista — lo mismo que escribir order(values[0], values[1]). **fields desempaqueta el diccionario, y sus claves tienen que coincidir con los nombres de los parámetros.
Un asterisco en una definición significa reunir; un asterisco en una llamada significa desempaquetar — el mismo símbolo, dos direcciones.
Un ejemplo completo
invoice.py:
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
total = sum(price for _, price in items)
after = total * (1 - discount)
lines = [f"Invoice for {customer}"]
for name, price in items:
lines.append(f" {name:<10} {price:>8.2f} {currency}")
lines.append(f" {'subtotal':<10} {total:>8.2f} {currency}")
if discount:
lines.append(f" {'discount':<10} {discount * 100:>7.0f}%")
lines.append(f" {'total':<10} {after:>8.2f} {currency}")
for key, value in extra.items():
lines.append(f" note {key}: {value}")
return "\n".join(lines)
print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))Invoice for rafi
pen 15.00 unit
bag 850.00 unit
subtotal 865.00 unit
total 865.00 unit
Invoice for dia
ink 120.00 unit
subtotal 120.00 unit
discount 10%
total 108.00 unit
note reference: A-77Cuatro cosas que vale la pena notar.
customer es obligatorio y todo lo demás es opcional. Una factura sin cliente no tiene sentido, así que es un parámetro simple. Puede que no haya items, así que *items.
discount=0.0 no es solo un valor por defecto, simplifica una condición. La línea if discount: usa la regla de veracidad del capítulo ocho — 0.0 es falso, así que sin descuento esa fila nunca se imprime. Y como discount viene después de *items, es solo por palabra clave — hay que pasarlo como discount=0.1.
`extra absorbe lo que la función no conoce.** reference="A-77" no coincide con ningún parámetro, así que cae en extra` y se imprime como nota. Así es como una función acepta campos que nadie ha pensado aún.
for _, price in items es el desempaquetado del capítulo catorce, con el guion bajo diciendo que el nombre no hace falta aquí. items es una tupla de tuplas, y cada tupla interna se desempaqueta.
Cuando falla
SyntaxError: parameter without a default follows parameter with a default Un parámetro con valor por defecto se colocó antes de uno obligatorio. Primero los obligatorios, luego los valores por defecto.
TypeError: order() takes 2 positional arguments but 3 were given Demasiados argumentos. El mensaje dice cuántos toma y cuántos llegaron.
TypeError: order() got an unexpected keyword argument 'qty' Se pasó por nombre, pero ningún parámetro tiene ese nombre. Revisa la ortografía — o ten en cuenta que una función con **kwargs no se habría quejado, lo habría absorbido en silencio.
TypeError: order() got multiple values for argument 'item' Al mismo parámetro se le dio dos veces, una por posición y otra por nombre — order("pen", item="bag").
La función se ejecuta pero la respuesta sale invertida Los argumentos se invirtieron, y no apareció error porque el código dentro de la función funciona con cualquiera de los dos valores. Pasa por nombre — este es el error silencioso que abrió este capítulo.
*Desempaquetar con ` pero las cantidades no coinciden** En order(*values), values debe contener exactamente tantas cosas como parámetros tenga la función. Comprueba con print(len(values))`.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
quantity tiene un valor por defecto. ¿Qué imprimen las dos líneas?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- CUn `TypeError`
- Dpen x 1 pen x 5
¿Qué tipo de cosa reúne *things?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'tuple'> 1
- C<class 'list'> 3
- D<class 'dict'> 3
El parámetro con valor por defecto se pone primero. ¿Qué pasa?
def order(quantity=1, item):
return item- AFunciona normalmente
- BFunciona, pero llamar a `order("pen")` falla
- CUn `SyntaxError` — un parámetro con valor por defecto no puede preceder a uno obligatorio
- DUn `TypeError`
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Escribe un archivo llamado message.py con una función:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)Debe devolver un mensaje legible en lugar de imprimirlo, con el destinatario, el asunto, cada línea en su propia fila, y cada par de details como nota debajo. Si urgent es True, añade algo antes del asunto.
Luego llámala de cuatro formas:
- Con solo un destinatario y ninguna línea
- Con tres líneas y todo lo demás en su valor por defecto
- Con
urgent=Truey unsubject, ambos por nombre - Construyendo una lista y un diccionario y desempaquetándolos con
*y**
Luego rómpela tres veces a propósito, y mira qué pasa cada vez:
- Mueve el parámetro
subject="Notice"antes derecipient. Antes de que el archivo se ejecute ya obtienesSyntaxError: parameter without a default follows parameter with a default. - Escribe
notify("rafi", subjekt="hi"), mal escrito. No hay error:**detailsacepta en silencio el nombre equivocado también, así quesubjektse convierte en una nota y el asunto se queda en"Notice". Imprime el mensaje devuelto y compruébalo tú mismo. - Escribe
notify("rafi", recipient="dia"). ObtienesTypeError: notify() got multiple values for argument 'recipient'.
Python detecta dos de estos fallos a la vez y deja pasar uno — y ese es el más peligroso. Este es el precio de la comodidad que da **kwargs: ya nadie comprueba la ortografía. Reconoce los mensajes una vez y los pillarás en segundos después, y también sabrás dónde se esconde el error silencioso.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz