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.

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
*argsgathers extra positional arguments into a tuple.**kwargsgathers extra named arguments into a dict.- Anything after
*or*argsis keyword-only. - When calling,
*listand**dictspread the values out. - The names
argsandkwargsare 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.