068. Keyword Arguments
Named arguments make call sites self-documenting
068. Keyword Arguments
Aryan’s check_threshold function has several parameters. When he calls it, positional arguments are a readability problem:
# What does True mean here? What is 1000? Unclear.
result = check_threshold("Chrome", 750, 500, 1000, True, "email")With keyword arguments, every call is self-documenting:
result = check_threshold(
process_name="Chrome",
rss_mb=750,
warn_mb=500,
crit_mb=1000,
alert=True,
channel="email",
)Keyword arguments also let him skip defaults he doesn’t want to override:
# Only override the critical threshold — leave warn at default
result = check_threshold("Chrome", rss_mb=750, crit_mb=800)The rule: positional arguments must come before keyword arguments at the call site. f(1, b=2) is valid. f(a=1, 2) raises SyntaxError.
💡 Fun fact: Python’s keyword arguments were inspired by Smalltalk’s named message parameters. The def f(a, *, b): syntax — which forces callers to use b=value — was added in Python 3 specifically to prevent API misuse in functions like sorted(items, key=..., reverse=...) where accidentally passing a boolean positionally would be disastrous.
⚠️ Watch out: The most common mistake is accidentally passing a keyword argument value to the wrong parameter when mixing positional and keyword args. For example, send_email("bob", "body", "Subject", "high") silently maps "high" to cc, not priority — no error, wrong output.
🤔 Think about it: Python allows f(a=1, b=2) even when the function is defined as def f(a, b). Should keyword arguments always be optional syntax sugar, or are there cases where requiring keyword arguments (with *) makes an API safer?
Learning objectives
- Call functions with keyword arguments
- Understand when keyword args improve readability
- Mix positional and keyword arguments correctly
Key concepts
- keyword arguments
- named arguments
- call site clarity
Try it
Concept detail
Keyword arguments are passed as name=value. Benefits:
- Order doesn’t matter: f(b=2, a=1) works even if def f(a, b).
- Skip defaults selectively: send_email(to, subject, body, priority=“high”) leaves cc and bcc at their defaults.
- Self-documenting: send_email(priority=“high”) is clearer than send_email(“high”).
Rules: Positional args MUST come before keyword args: f(1, b=2) OK; f(a=1, 2) SyntaxError Cannot pass the same arg twice: f(1, a=1) raises TypeError
Keyword-only arguments (after *): def f(a, *, b): # b MUST be passed as keyword … f(1, b=2) # OK f(1, 2) # TypeError: b must be keyword
When to use keyword args:
- Functions with more than 2-3 parameters
- Boolean/flag parameters: open(path, errors=“ignore”) not open(path, “ignore”)
- Any time the meaning isn’t obvious from position
Solution
def send_email(to, subject, body, cc=None, bcc=None, priority="normal"):
return {
"to": to, "subject": subject, "body": body,
"cc": cc, "bcc": bcc, "priority": priority
}
msg1 = send_email("[email protected]", "Hello", "Hi there")
msg2 = send_email("[email protected]", subject="Meeting Notes", body="body text", priority="high")
msg3 = send_email("[email protected]", "Update", "Please review", priority="urgent")Tests
def test_msg1_basic():
assert msg1["to"] == "[email protected]"
assert msg1["subject"] == "Hello"
assert msg1["body"] == "Hi there"
def test_msg2_subject_correct():
assert msg2["subject"] == "Meeting Notes"
def test_msg2_body_correct():
assert msg2["body"] == "body text"
def test_msg2_priority():
assert msg2["priority"] == "high"
def test_msg3_priority():
assert msg3["priority"] == "urgent"
assert msg3["subject"] == "Update"
def test_defaults():
msg = send_email("[email protected]", "Hi", "Body")
assert msg["cc"] is None
assert msg["priority"] == "normal"