← Home

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:

  1. Order doesn’t matter: f(b=2, a=1) works even if def f(a, b).
  2. Skip defaults selectively: send_email(to, subject, body, priority=“high”) leaves cc and bcc at their defaults.
  3. 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"

Resources