Course Content
Python Essentials for AI Engineer
6 sections · 48 lessons
What are *args and **kwargs?
What you need to know
Packing in a definition
1def log(*args, **kwargs):2 print(args) # a tuple of positional arguments3 print(kwargs) # a dict of keyword arguments45log("upi", "failed", level="INFO", retry=True)6# ('upi', 'failed')7# {'level': 'INFO', 'retry': True}The names args and kwargs are only convention; the stars do the work. The order in a definition is fixed: normal parameters, *args, keyword-only parameters, **kwargs.
Unpacking at a call site
1def create(model, temperature, max_tokens):2 return f"{model} {temperature} {max_tokens}"34positional = ["chat-small", 0.2, 256]5config = {"model": "chat-large", "temperature": 0.0, "max_tokens": 512}6print(create(*positional)) # chat-small 0.2 2567print(create(**config)) # chat-large 0.0 51289defaults = {"temperature": 0.2, "max_tokens": 256}10override = {"max_tokens": 1024}11print({**defaults, **override}) # {'temperature': 0.2, 'max_tokens': 1024}{**a, **b} merges dicts, with later keys winning — the same result as a | b in Python 3.9+.
The cost of flexibility
A function that takes **kwargs accepts any name, including typos. call(temprature=0) does not fail; the value is just never used. For a public function, an explicit signature is safer; use **kwargs when you really are forwarding arguments you don't own.
A real-life example
LLM APIs fail now and then with timeouts and rate limits. A generic retry wrapper must work for any function with any arguments, which is exactly what *args and **kwargs are for:
1import time23def with_retry(fn, *args, attempts=3, delay=0.1, **kwargs):4 for attempt in range(1, attempts + 1):5 try:6 return fn(*args, **kwargs) # forward everything7 except TimeoutError:8 if attempt == attempts:9 raise10 time.sleep(delay * 2 ** (attempt - 1)) # 0.1s, 0.2s, ...1112calls = {"n": 0}13def flaky_llm(prompt, *, temperature=0.2):14 calls["n"] += 115 if calls["n"] < 3:16 raise TimeoutError("upstream timeout")17 return f"answer to {prompt!r} at T={temperature}"1819print(with_retry(flaky_llm, "Where is my refund?", temperature=0.0))20# answer to 'Where is my refund?' at T=0.021print(calls["n"]) # 3with_retry never needs to know flaky_llm's signature. The same wrapper works for an embedding call, a vector-database query or a file upload. Notice that attempts and delay are keyword-only (they come after *args), so they never collide with the wrapped function's arguments.
Follow-up questions to expect
- "What order do parameters go in?" — Positional parameters,
*args, keyword-only parameters, then**kwargs. - "Can you use names other than
argsandkwargs?" — Yes,*itemsand**optionswork the same. The convention just helps readers. - "What is the risk of
**kwargs?" — Misspelt keyword arguments are silently accepted, and the signature no longer documents what the function needs.