← Home

070. '**Kwargs (Variable Keyword Arguments)'

Accept arbitrary named configuration options

070. ‘**Kwargs (Variable Keyword Arguments)’

Aryan’s monitor needs to be configurable without hardcoding every possible option as a parameter. **kwargs lets him accept arbitrary named settings:

def start_monitor(target, **options):
    """Start monitoring target with optional configuration."""
    config = {
        "interval": 5,       # default polling interval
        "warn_mb":  500,
        "crit_mb":  1000,
    }
    config.update(options)   # caller overrides win
    print(f"Monitoring {target} | interval={config['interval']}s")
    return config

# Minimal call
start_monitor("Chrome")

# Custom thresholds
start_monitor("PyCharm", warn_mb=300, crit_mb=800, interval=2)

He also uses **kwargs for a wrapper that forwards all options to the underlying alert sender:

def send_alert(level, message, **delivery):
    """level + message are required; delivery options are flexible."""
    return {
        "level":   level,
        "message": message,
        **delivery          # unpack kwargs into the result dict
    }

send_alert("CRITICAL", "Chrome at 1.2 GB", channel="slack", mention="@on-call")

**kwargs is a dict inside the function — iterate with .items(), access with kwargs["key"], or unpack with **kwargs.


💡 Fun fact: The ** unpacking syntax in Python is powerful enough to merge two dicts in one expression: {**dict_a, **dict_b}. This pattern, used everywhere from config merging to API wrappers, was popularized by PEP 448 in Python 3.5. Before that, you needed dict_a.update(dict_b) which mutated the original.

⚠️ Watch out: **kwargs keys must be valid Python identifiers — you cannot do f(**{"my-key": 1}) because my-key is not a valid variable name. This trips up developers working with HTTP headers or JSON keys that use hyphens.

Learning objectives

  • Use **kwargs to accept arbitrary keyword arguments
  • Access **kwargs as a dict inside the function
  • Combine *args and **kwargs for fully flexible functions

Key concepts

  • **kwargs
  • keyword arguments
  • dict

Try it

Concept detail

**kwargs in a function definition collects extra keyword arguments into a dict.

def f(**kwargs): — kwargs is a dict, possibly empty def f(a, b, **rest): — a and b are normal params; rest gets extra keyword args

Inside the function: kwargs[“key”] — access a specific key kwargs.get(“key”, default) — safe access for k, v in kwargs.items() — iterate all pairs config.update(kwargs) — merge into another dict

Calling with a pre-built dict (unpacking): options = {“port”: 80, “debug”: True} f(**options) — same as f(port=80, debug=True)

Common patterns:

  1. Config builder: required params + **options for overrides
  2. Wrapper/forwarder: capture kwargs and pass to inner function
  3. Flexible logger: log(level, message, **context)

Combining *args and **kwargs: def f(*args, **kwargs): — accepts anything def wrapper(*args, **kwargs): return original(*args, **kwargs) # perfect forwarding

merge_dicts(**dicts): each keyword arg IS a dict — kwargs is {name: dict_value}. Iterate kwargs.values() to get the actual dicts to merge.

Solution

def build_config(name, **settings):
    config = {"name": name}
    config.update(settings)
    return config

def merge_dicts(**dicts):
    result = {}
    for d in dicts.values():
        result.update(d)
    return result

def log_event(event_type, **details):
    parts = [f"{k}={v}" for k, v in details.items()]
    return f"EVENT {event_type}: {', '.join(parts)}"

Tests

def test_build_config_basic():
    config = build_config("myapp", host="localhost", port=8080)
    assert config["name"] == "myapp"
    assert config["host"] == "localhost"
    assert config["port"] == 8080

def test_build_config_no_extras():
    config = build_config("simple")
    assert config == {"name": "simple"}

def test_build_config_many_extras():
    config = build_config("app", a=1, b=2, c=3)
    assert config["a"] == 1 and config["b"] == 2 and config["c"] == 3

def test_merge_dicts():
    result = merge_dicts(a={"x": 1}, b={"y": 2})
    assert result["x"] == 1
    assert result["y"] == 2

def test_merge_dicts_overlap():
    # Later dict wins on key conflict
    result = merge_dicts(first={"key": "old"}, second={"key": "new"})
    assert result["key"] == "new"

def test_log_event():
    msg = log_event("login", user="alice", status="success")
    assert "EVENT login:" in msg
    assert "user=alice" in msg
    assert "status=success" in msg

Resources