Python
kwargs
dictionary
function arguments
Python programming

Converting Python dict to kwargs?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, the double-asterisk ** operator lets you unpack a dictionary into keyword arguments when calling a function. This means you can build up a dictionary of parameters and then pass them all at once, which is especially useful when working with configuration objects, API wrappers, or any situation where the set of arguments is determined at runtime. Understanding this mechanism is fundamental to writing flexible, maintainable Python code.

Basic **dict Syntax

The ** operator takes a dictionary and expands its key-value pairs into keyword arguments. Each key becomes a parameter name, and each value becomes the corresponding argument.

python
1def greet(name, greeting):
2    print(f"{greeting}, {name}!")
3
4params = {"name": "Alice", "greeting": "Hello"}
5greet(**params)  # Output: Hello, Alice!

This is exactly equivalent to calling greet(name="Alice", greeting="Hello"). The dictionary keys must be strings and must match the function's parameter names.

How It Works Under the Hood

When Python encounters **dict in a function call, it iterates over the dictionary and passes each key-value pair as a keyword argument. If a key does not match any parameter in the function signature, Python raises a TypeError. If a key duplicates a positional argument you already provided, Python also raises a TypeError.

python
1def add(a, b):
2    return a + b
3
4args = {"a": 3, "b": 7}
5result = add(**args)  # Returns 10
6
7# This raises TypeError: got multiple values for argument 'a'
8# add(1, **{"a": 2, "b": 3})

Combining with Positional Arguments

You can mix positional arguments with **dict unpacking. Positional arguments are matched first, then the dictionary fills in the remaining keyword arguments.

python
1def create_user(name, age, email, role="member"):
2    return {"name": name, "age": age, "email": email, "role": role}
3
4extra = {"email": "[email protected]", "role": "admin"}
5user = create_user("Alice", 30, **extra)
6print(user)
7# {'name': 'Alice', 'age': 30, 'email': '[email protected]', 'role': 'admin'}

You can also combine *args unpacking with **kwargs unpacking in the same call:

python
1def func(a, b, c, d):
2    return a + b + c + d
3
4positional = [1, 2]
5keyword = {"c": 3, "d": 4}
6result = func(*positional, **keyword)  # Returns 10

**kwargs in Function Definitions

On the receiving side, **kwargs in a function definition collects any extra keyword arguments into a dictionary. This is the mirror image of unpacking.

python
1def log_event(event_type, **kwargs):
2    print(f"Event: {event_type}")
3    for key, value in kwargs.items():
4        print(f"  {key}: {value}")
5
6log_event("login", user="Alice", ip="192.168.1.1", timestamp="2025-01-15")
7# Event: login
8#   user: Alice
9#   ip: 192.168.1.1
10#   timestamp: 2025-01-15

You can forward these collected kwargs to another function:

python
1def wrapper(**kwargs):
2    # Add a default and forward everything
3    kwargs.setdefault("timeout", 30)
4    return make_request(**kwargs)

Merging Dictionaries with **

Since Python 3.5, you can use ** inside dictionary literals to merge dictionaries. This creates a new dictionary combining all key-value pairs:

python
1defaults = {"timeout": 30, "retries": 3, "verbose": False}
2overrides = {"timeout": 60, "verbose": True}
3
4config = {**defaults, **overrides}
5print(config)
6# {'timeout': 60, 'retries': 3, 'verbose': True}

Later dictionaries overwrite earlier ones for duplicate keys. In Python 3.9 and later, you can also use the | operator:

python
config = defaults | overrides  # Same result, Python 3.9+

TypedDict and Type Hints

When using **dict unpacking with type checkers like mypy, you can use TypedDict to define the expected shape of the dictionary:

python
1from typing import TypedDict
2
3class RequestConfig(TypedDict):
4    url: str
5    method: str
6    timeout: int
7
8def make_request(url: str, method: str, timeout: int) -> None:
9    print(f"{method} {url} (timeout={timeout}s)")
10
11config: RequestConfig = {
12    "url": "https://api.example.com/data",
13    "method": "GET",
14    "timeout": 30
15}
16make_request(**config)  # Type checker knows this is valid

With TypedDict and the Unpack type (Python 3.12+ or typing_extensions), you can also type-hint **kwargs:

python
1from typing import Unpack
2
3class Options(TypedDict, total=False):
4    verbose: bool
5    retries: int
6
7def process(**kwargs: Unpack[Options]) -> None:
8    print(kwargs)

Real-World Examples

Configuration Dictionaries

python
1db_config = {
2    "host": "localhost",
3    "port": 5432,
4    "database": "myapp",
5    "user": "admin",
6    "password": "secret"
7}
8
9import psycopg2
10conn = psycopg2.connect(**db_config)

API Call Wrappers

python
1import requests
2
3def fetch_data(endpoint, **kwargs):
4    defaults = {"timeout": 10, "headers": {"Accept": "application/json"}}
5    merged = {**defaults, **kwargs}
6    return requests.get(endpoint, **merged)
7
8response = fetch_data("https://api.example.com/users", timeout=30)

Factory Functions

python
1def create_button(text, **style_kwargs):
2    default_style = {"bg": "white", "fg": "black", "font_size": 14}
3    style = {**default_style, **style_kwargs}
4    return Button(text=text, **style)
5
6btn = create_button("Submit", bg="blue", fg="white")

Common Pitfalls

  • Using non-string keys in the dictionary, which raises a TypeError since keyword arguments must be strings.
  • Passing a key in the dictionary that duplicates a positional argument, causing "got multiple values for argument" errors.
  • Mutating the original dictionary after unpacking, which has no effect since unpacking copies the values at call time.
  • Assuming dictionary order matters for the function call; it does not, since keyword arguments are matched by name, not position.
  • Forgetting that **kwargs collects extras into a plain dict, losing any TypedDict type information at runtime.

Summary

  • Use **dict in a function call to unpack a dictionary into keyword arguments.
  • Combine with positional arguments and *list unpacking for full flexibility.
  • Use **kwargs in function definitions to accept arbitrary keyword arguments as a dictionary.
  • Merge dictionaries with {**a, **b} (Python 3.5+) or a | b (Python 3.9+).
  • Use TypedDict with type checkers to maintain type safety when unpacking dictionaries into function calls.

Course illustration
Course illustration

All Rights Reserved.