When defining a function, there are cases where you want to accept an arbitrary number of “optional settings” or “additional attributes” apart from the mandatory arguments. While *args (introduced in the previous article) receives “positional arguments” as a tuple, if you want to receive a set of argument names (keys) and values as a dictionary, you use **kwargs.
This article explains the basic usage of **kwargs and the important rules when using it in combination with *args.
What are Variable-Length Keyword Arguments (**kwargs)?
If you add two asterisks (**) before an argument name in a function definition, that argument receives “all remaining keyword arguments” passed during the call as a dictionary (dict). By convention, the variable name kwargs (short for keyword arguments) is used, but you can use any name like **options or **attributes.
Basic Usage
As a specific example, let’s consider a function to create a user profile. The name and email address are mandatory, but other attributes (age, job, address, etc.) vary by user, so we receive them flexibly with **kwargs.
def create_user_profile(username, email, **attributes):
"""
Accept mandatory info and arbitrary additional attributes to display a profile.
"""
print(f"--- User: {username} ---")
print(f"Email: {email}")
# attributes is received as a dictionary
print(f"Number of additional attributes: {len(attributes)}")
print(f"Type: {type(attributes)}")
# Since it's a dict, we can loop with .items()
for key, value in attributes.items():
print(f" {key}: {value}")
# --- Calling the function ---
# 1. Mandatory arguments only
create_user_profile("Suzuki", "suzuki@example.com")
print("\n")
# 2. Mandatory args + arbitrary keyword args
create_user_profile(
"Tanaka",
"tanaka@example.com",
age=30,
job="Engineer",
city="Tokyo"
)
Output:
--- User: Suzuki ---
Email: suzuki@example.com
Number of additional attributes: 0
Type: <class 'dict'>
--- User: Tanaka ---
Email: tanaka@example.com
Number of additional attributes: 3
Type: <class 'dict'>
age: 30
job: Engineer
city: Tokyo
Arguments like age=30 are treated inside the create_user_profile function as a dictionary: {'age': 30, 'job': 'Engineer', 'city': 'Tokyo'}.
Combining Positional, *args, and **kwargs
In Python functions, you can combine all three types of arguments. However, there is a strict rule regarding the order of definition. You must write them in the following order:
- Positional Arguments (Normal arguments)
*args(Variable-length positional arguments)**kwargs(Variable-length keyword arguments)
Syntax:
def function_name(positional_args, *args, **kwargs):
# Process
Specific Example
Here is an example of a log output function that receives severity (positional), message content (variable-length positional), and metadata (variable-length keyword) all at once.
def write_system_log(severity, *messages, **metadata):
print(f"[{severity}]")
# Process messages (tuple)
for msg in messages:
print(f" Message: {msg}")
# Process metadata (dict)
if metadata:
print(" Metadata:")
for key, val in metadata.items():
print(f" {key} = {val}")
# --- Calling ---
write_system_log(
"ERROR", # severity (positional)
"Connection failed", # *messages (1st)
"Timeout occurred", # *messages (2nd)
user_id=101, # **metadata
timestamp="12:00:00" # **metadata
)
Output:
[ERROR]
Message: Connection failed
Message: Timeout occurred
Metadata:
user_id = 101
timestamp = 12:00:00
If you do not follow this order, a SyntaxError will occur.
Unpacking Dictionaries to Pass
If you want to pass data you already hold as a dictionary to a function as keyword arguments, use ** at the calling side to unpack (expand) the dictionary.
# Dictionary data
user_info = {
"age": 25,
"job": "Designer",
"city": "Osaka"
}
# Passing the dict as is causes an error (argument count mismatch or treated as positional)
# create_user_profile("Sato", "sato@example.com", user_info)
# Add ** to unpack and pass
create_user_profile("Sato", "sato@example.com", **user_info)
This is treated exactly the same as specifying age=25, job="Designer", city="Osaka" individually.
Summary
- Adding
**to an argument (e.g.,**kwargs) allows you to accept an arbitrary number of keyword arguments as a dictionary. - The definition order is critical:
def func(arg, *args, **kwargs):. - Adding
**to a dictionary at the calling side allows you to unpack it as keyword arguments and pass it to the function. - This feature is a very powerful tool when creating API wrapper functions or functions with many configuration options.
