Python **kwargs Explained

Learn how *kwargs lets Python functions accept any number of keyword arguments, how it works as a dictionary, and how to combine it with args for maximum flexibility.

7 min read

Where *args handles a variable number of positional arguments, **kwargs handles a variable number of keyword arguments. The double asterisk syntax collects any keyword arguments that were not matched to named parameters and stores them in a dictionary. If *args is about accepting any number of things, **kwargs is about accepting any named configuration, and together they give a Python function the ability to handle whatever arguments a caller might pass, now or in the future.

The name kwargs is a strong convention that stands for "keyword arguments," and like args, you can technically use any valid identifier after the double asterisk. But every Python developer recognizes kwargs instantly, and using a different name will slow down anyone reading your code. The double asterisk is the dictionary unpacking operator, and when used in a function definition it means "collect remaining keyword arguments into a dictionary." When used at a call site, as you will see later, it means the opposite: unpack a dictionary into individual keyword arguments. This duality is the same pattern that the single asterisk follows with *args and iterable unpacking.

If you have been following this section in order, you have already learned about Python *args explained and how it collects positional arguments into a tuple. **kwargs does the same job for keyword arguments, collecting them into a dictionary, and the two features are often used together in functions that need to accept and forward any combination of arguments without knowing in advance what those arguments will be.

How **kwargs collects extra keyword arguments

When you place **kwargs at the end of a function's parameter list, Python takes any keyword arguments that were not matched to named parameters and stores them as key-value pairs in a dictionary. The argument names become the dictionary keys as strings, and the argument values become the dictionary values, preserving their original types:

pythonpython
def build_profile(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")
 
build_profile(name="Ada", role="developer", years=5)

The three keyword arguments are collected into a dictionary that maps "name" to "Ada", "role" to "developer", and "years" to 5. The function iterates over the dictionary and prints each entry. If you call build_profile with no arguments, the kwargs dictionary is empty and the loop simply does not execute. If you call it with ten keyword arguments, all ten appear in the dictionary and are processed uniformly. This flexibility makes **kwargs ideal for functions that act as configuration hubs, accepting whatever options a caller needs to set and forwarding them to the appropriate subsystems.

The kwargs dictionary supports all standard dictionary operations. You can check for the presence of a key with the in operator, retrieve a value with kwargs.get("name", default_value) to handle missing keys gracefully, merge it with another dictionary using the update method, and iterate over keys, values, or both. The dictionary is a regular dict object, which means it is mutable; the function can modify it, add default values for missing keys, or remove entries before forwarding it to another function.

Where **kwargs fits in the parameter list

The position of **kwargs is the simplest rule of all: it must come last. After required parameters, after *args, and after keyword-only parameters with defaults, **kwargs sits at the very end and collects everything that remains. This ordering is enforced by the parser, and no other arrangement is valid:

pythonpython
def process(action, *args, timeout=30, **kwargs):
    print(f"Action: {action}")
    print(f"Args: {args}")
    print(f"Timeout: {timeout}")
    print(f"Extra: {kwargs}")
 
process("deploy", "staging", "quiet", timeout=60, region="us-east")

The argument "deploy" fills the required action parameter. The strings "staging" and "quiet" go into the args tuple. The timeout=60 matches the named timeout parameter and overrides its default of 30. The remaining keyword argument region="us-east" does not match any named parameter, so it goes into the kwargs dictionary as the key "region" with the value "us-east". This layered collection, required first, then positional extras, then named options, then anything else, is the full pattern for functions that handle their own arguments while still accepting unknown future arguments.

A common mistake is to place **kwargs before a named parameter. The parser rejects this because **kwargs consumes all remaining keyword arguments greedily, leaving nothing for a subsequent parameter to match. If you need both a specific keyword parameter and generic **kwargs, the specific parameter must appear before **kwargs in the parameter list, either as a regular keyword parameter with a default or as part of a keyword-only section after an asterisk.

Using **kwargs for argument forwarding

The most powerful use of **kwargs is forwarding keyword arguments from one function to another. Combined with *args for positional arguments, **kwargs enables wrapper functions that add behavior without needing to know the full signature of the function they wrap:

pythonpython
def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

The wrapper function accepts *args and **kwargs, capturing everything the caller passes, then forwards both to func using the unpacking operators. The wrapper adds logging before the call but does not inspect, modify, or even name the arguments; it simply passes them through. This pattern is the foundation of Python decorators, which are covered in depth later in this learning path in the article on creating your first Python decorator.

The forwarding pattern works because Python matches arguments to parameters at the call site based on how they are presented. When wrapper(*args, **kwargs) calls func, the * unpacks args into positional arguments and the ** unpacks kwargs into keyword arguments. Func's own parameter list then processes these arguments according to its own rules, with no awareness that they passed through an intermediate function. This transparency is what makes it possible to stack multiple wrappers around a function, each adding one concern, without any wrapper needing to understand the function's specific parameter list.

The double asterisk at the call site: dictionary unpacking

Just as a single asterisk unpacks an iterable into positional arguments at a call site, a double asterisk unpacks a dictionary into keyword arguments. This is the mirror image of kwargs in a definition, and it lets you build argument dictionaries dynamically and pass them to functions. Writing start_server({"host": "localhost", "port": 5432, "debug": True}) is equivalent to calling start_server(host="localhost", port=5432, debug=True). Each key-value pair becomes a keyword argument, and the keys must be strings that match the target function's parameter names, though the dictionary can contain extra keys that are not parameters if the function accepts **kwargs. This pattern is common in configuration-driven applications where settings are loaded from files or environment variables and passed to functions without manually unpacking every value.

Calling func(**some_dict) also provides a clean way to merge default values with overrides. Build a dictionary of defaults, update it with caller-provided overrides, and unpack the result into the target function. The function receives the merged configuration without any awareness that the values came from a dictionary rather than from literal keyword arguments in the source code.

Combining *args and **kwargs for maximum flexibility

When a function needs to accept any combination of positional and keyword arguments and forward them to another function, the signature def wrapper(*args, **kwargs) is the standard solution. This pattern appears in decorators, method overrides, callback dispatchers, and any interface that sits between a caller and a callee without knowing the callee's exact signature at the time the wrapper is written:

pythonpython
def call_any(func, *args, **kwargs):
    print(f"Calling {func.__name__} with {len(args)} positional and {len(kwargs)} keyword args")
    return func(*args, **kwargs)
 
result = call_any(pow, 2, 10)           # pow(2, 10) -> 1024
result = call_any(sorted, [3, 1, 2], reverse=True)

The call_any function accepts a function and any arguments, logs the call, and invokes the function with the provided arguments. It works with pow, sorted, or any other callable, regardless of that callable's parameter list, because *args and **kwargs are completely transparent to both the caller and the callee. The caller passes arguments normally, the callee receives them normally, and call_any sits in the middle adding a logging side effect.

The combination of *args and **kwargs is powerful but should be used deliberately. Functions that accept *args and **kwargs and do nothing but forward them are useful abstractions. Functions that accept *args and **kwargs and then inspect, validate, or transform individual arguments are harder to understand because the function's expectations are hidden in runtime logic rather than declared in the signature. When the set of expected arguments is known, even if it is large, prefer named parameters with defaults. Reserve *args and **kwargs for the cases where the argument set is genuinely open-ended or entirely determined by a downstream function whose signature may change independently.

Rune AI

Rune AI

Key Insights

  • **kwargs in a function definition collects extra keyword arguments into a dictionary named kwargs.
  • Each keyword name becomes a dictionary key, and the argument value becomes the corresponding value.
  • Place **kwargs last in the parameter list, after required parameters, *args, and keyword-only parameters.
  • The ** operator at a call site unpacks a dictionary into individual keyword arguments.
  • Combining *args and **kwargs creates functions that can accept and forward any combination of arguments.
RunePowered by Rune AI

Frequently Asked Questions

What does **kwargs do in a Python function definition?

The **kwargs syntax collects any extra keyword arguments passed to a function into a dictionary named kwargs. Each keyword argument name becomes a dictionary key, and the argument value becomes the dictionary value. The double asterisk is the dictionary unpacking operator, and kwargs is the conventional name, though any valid identifier works.

Can a function use both *args and **kwargs?

Yes, and the order must be: required parameters, then *args, then keyword-only parameters with defaults, then **kwargs. When calling such a function, positional arguments fill required parameters and *args, keyword arguments that match parameter names fill those parameters, and any unmatched keyword arguments go into **kwargs.

When should I use **kwargs instead of named parameters with defaults?

Use **kwargs when the set of possible keyword arguments is genuinely open-ended and cannot be known in advance. Forwarding arguments to another function, building configuration systems, and wrapping APIs that may add new parameters in future versions are all legitimate uses. If the set of accepted keyword arguments is fixed and known, use named parameters with defaults instead; they are more self-documenting and give better IDE support.

Conclusion

**kwargs completes the flexibility that *args begins. Together, they let a Python function accept any combination of positional and keyword arguments, making it possible to write wrappers, dispatchers, and configuration systems that adapt to changing requirements without changing the function signature. The power of **kwargs comes with the responsibility to use it only when the flexibility is genuinely needed, not as a shortcut to avoid thinking about your function's interface.