DMarlon Studio

Lesson 2 · 2 of 20

*args, **kwargs and keyword-only arguments · Python

Python functions that accept any number of arguments with *args and **kwargs, plus arguments that can only be passed by name.

Python · *args, **kwargs and keyword-only arguments in DMarlon Studio: the example's code and its output in the terminal

invoice_total(*amounts, ...) takes as many amounts as you give it: the asterisk gathers them into a tuple called amounts, and sum(amounts) adds them up. That is why it works the same with three amounts or with one.

The parameters after *amounts (discount and tax) are keyword-only: there is no way to pass them by position, because *amounts swallows everything positional. You have to write discount=0.1 or tax=0.10, which also makes the call clearer.

log_event(event, **details) collects the leftover named arguments in a dictionary. The program loops over details.items() to print each field as key=value.

Asterisks also work when calling: invoice_total(*prices) spreads a list into separate arguments, and log_event("update", **extra) spreads a dictionary into named arguments.

In ship(order_id, *, express=False) the bare * collects nothing: it just marks that everything after it is keyword-only. So ship(1043, True) raises a TypeError, which the program catches and prints.

Code · main.py

def invoice_total(*amounts, discount=0.0, tax=0.21):    """Any number of amounts; discount and tax are keyword-only."""    net = sum(amounts) * (1 - discount)    return round(net * (1 + tax), 2)def log_event(event, **details):    """Extra named arguments arrive in a dict."""    fields = ", ".join(f"{key}={value}" for key, value in details.items())    print(f"[{event}] {fields}")def ship(order_id, *, express=False):    """The bare * makes express keyword-only."""    speed = "express" if express else "standard"    print(f"Order {order_id} ships by {speed} delivery")print("Total:", invoice_total(100, 50, 25))print("With 10% off:", invoice_total(100, 50, 25, discount=0.1))print("Reduced tax:", invoice_total(80, tax=0.10))prices = [19.99, 5.01, 75.0]print("From a list:", invoice_total(*prices))  # unpack a list into *amountslog_event("login", user="ana", ip="10.0.0.7")extra = {"order": 1042, "status": "shipped"}log_event("update", **extra)       # unpack a dict into **detailsship(1042, express=True)try:    ship(1043, True)               # positional: not allowedexcept TypeError as error:    print("Error:", error)

Output · python3 main.py

Total: 211.75
With 10% off: 190.57
Reduced tax: 88.0
From a list: 121.0
[login] user=ana, ip=10.0.0.7
[update] order=1042, status=shipped
Order 1042 ships by express delivery
Error: ship() takes 1 positional argument but 2 were given

Key points

  • *args gathers extra positional arguments into a tuple.
  • **kwargs gathers extra named arguments into a dict.
  • Anything after * or *args is keyword-only.
  • When calling, *list and **dict spread the values out.
  • The names args and kwargs are a convention; the asterisks are what matter.

Exercise

Add a keyword-only shipping=0.0 parameter to invoice_total that is added at the end, and call it with shipping=4.95.