Python Essentials for AI Engineers · Lesson 13 of 36
*args and **kwargs Explained
The Problem They Solve
Sometimes you don't know in advance how many arguments a function will receive:
Python
# Fixed arguments — can only add exactly 2 numbers
def add_two(a: float, b: float) -> float:
return a + b
# *args — can add any number of values
def add_all(*values: float) -> float:
return sum(values)
print(add_all(1.0)) # 1.0
print(add_all(1.0, 2.0, 3.0)) # 6.0
print(add_all(1.5, 2.5, 3.5, 4.5)) # 12.0*args: Variable Positional Arguments
*args collects extra positional arguments into a tuple:
Python
def log_drugs(*drug_names: str) -> None:
print(f"Logging {len(drug_names)} drugs:")
for drug in drug_names:
print(f" - {drug}")
log_drugs("warfarin")
# Logging 1 drugs:
# - warfarin
log_drugs("warfarin", "aspirin", "metformin")
# Logging 3 drugs:
# - warfarin
# - aspirin
# - metformin
# Inside the function, args is a tuple
def show_type(*args):
print(type(args)) # <class 'tuple'>
print(args) # ("warfarin", "aspirin")**kwargs: Variable Keyword Arguments
**kwargs collects extra keyword arguments into a dict:
Python
def create_patient_record(**fields) -> dict:
"""Create a patient record from any keyword arguments provided."""
return dict(fields)
record = create_patient_record(
patient_id="P001",
age=67,
inr=2.4,
medications=["warfarin", "aspirin"],
)
print(record)
# {"patient_id": "P001", "age": 67, "inr": 2.4, "medications": [...]}
# Inside the function, kwargs is a dict
def show_kwargs(**kwargs):
print(type(kwargs)) # <class 'dict'>
for key, value in kwargs.items():
print(f" {key} = {value}")
show_kwargs(model="gpt-4o", temperature=0, max_tokens=500)Combining Parameters
Order must be: positional → *args → keyword-only → **kwargs
Python
def configure_agent(
agent_name: str, # Required positional
*tools: str, # Variable positional — any number of tool names
model: str = "gpt-4o", # Keyword-only with default
max_iterations: int = 8,
**metadata, # Any extra keyword arguments as metadata
) -> dict:
return {
"name": agent_name,
"tools": tools,
"model": model,
"max_iterations": max_iterations,
"metadata": metadata,
}
result = configure_agent(
"clinical_agent",
"search_drug", "check_interaction", "format_summary", # Goes into *tools
model="gpt-4o",
max_iterations=6,
version="2.0", # Goes into **metadata
owner="pharmacy_team",
)
print(result["tools"]) # ("search_drug", "check_interaction", "format_summary")
print(result["metadata"]) # {"version": "2.0", "owner": "pharmacy_team"}Unpacking: * and ** in Calls
The same * and ** syntax can unpack collections when calling functions:
Python
def calculate_dose(drug: str, weight_kg: float, dose_per_kg: float) -> float:
return weight_kg * dose_per_kg
# Unpack a list/tuple as positional arguments
args = ("vancomycin", 70.0, 15.0)
dose = calculate_dose(*args) # Same as calculate_dose("vancomycin", 70.0, 15.0)
# Unpack a dict as keyword arguments
kwargs = {"drug": "vancomycin", "weight_kg": 70.0, "dose_per_kg": 15.0}
dose = calculate_dose(**kwargs) # Same as calculate_dose(drug="vancomycin", ...)
# Mix both
positional = ("vancomycin",)
keyword = {"weight_kg": 70.0, "dose_per_kg": 15.0}
dose = calculate_dose(*positional, **keyword)Unpacking in Other Contexts
Python
# Merge lists
list_a = [1, 2, 3]
list_b = [4, 5, 6]
merged = [*list_a, *list_b] # [1, 2, 3, 4, 5, 6]
# Merge dicts (Python 3.9+ also has | operator)
defaults = {"temperature": 0, "max_tokens": 500}
overrides = {"model": "gpt-4o", "temperature": 0.2}
config = {**defaults, **overrides}
# {"temperature": 0.2, "max_tokens": 500, "model": "gpt-4o"} — rightmost wins
# Unpack in list/tuple assignment
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
head, *middle, tail = [1, 2, 3, 4, 5]
print(middle) # [2, 3, 4]Real Uses in AI Frameworks
LangChain callbacks:
Python
from langchain_core.callbacks import BaseCallbackHandler
class MyCallback(BaseCallbackHandler):
def on_llm_start(self, serialized: dict, prompts: list, **kwargs) -> None:
# **kwargs captures extra arguments LangChain may pass in the future
# Without **kwargs, new LangChain versions adding extra args would break this
run_id = kwargs.get("run_id")
tags = kwargs.get("tags", [])Forwarding arguments without listing them:
Python
from langchain_openai import ChatOpenAI
def create_model(model_name: str, **model_kwargs) -> ChatOpenAI:
"""Create a ChatOpenAI with any supported kwargs forwarded."""
return ChatOpenAI(model=model_name, **model_kwargs)
# Caller can pass any ChatOpenAI argument
model = create_model(
"gpt-4o",
temperature=0,
max_tokens=500,
timeout=30,
# Any future ChatOpenAI parameter works without changing create_model
)
# Decorator pattern — wrapping functions
import time
from functools import wraps
def timed(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # Forward all args/kwargs to original function
elapsed = round((time.time() - start) * 1000)
print(f"{func.__name__} took {elapsed}ms")
return result
return wrapper
@timed
def embed_text(text: str, model: str = "text-embedding-3-small") -> list[float]:
... # Whatever the real implementation doesCommon Patterns
Python
# 1. Super() in class hierarchies — always use *args, **kwargs
class ClinicalAgent:
def __init__(self, specialty: str, **kwargs):
self.specialty = specialty
class PharmacistAgent(ClinicalAgent):
def __init__(self, formulary_access: bool = True, **kwargs):
super().__init__(**kwargs) # Pass remaining kwargs up the chain
self.formulary_access = formulary_access
agent = PharmacistAgent(formulary_access=True, specialty="oncology")
# 2. Config forwarding
def run_experiment(model: str, dataset: str, **hyperparams) -> dict:
"""hyperparams captured and forwarded to the training function."""
results = train_model(model, dataset, **hyperparams)
return results
run_experiment("gpt-4o", "clinical_qa", temperature=0, max_tokens=200, batch_size=32)
# 3. Flexible logging
def log(*messages: str, level: str = "INFO", **context) -> None:
prefix = f"[{level}]"
text = " ".join(str(m) for m in messages)
meta = " ".join(f"{k}={v}" for k, v in context.items())
print(f"{prefix} {text} | {meta}")
log("Agent invoked", "warfarin query", level="DEBUG", session_id="abc123", user="pharmacist")
# [DEBUG] Agent invoked warfarin query | session_id=abc123 user=pharmacist