Skip to Content
Course content

69: Variable-Length Keyword Arguments with **kwargs

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was building a wrapper for a third-party email marketing API. The API was... flexible, to say the least. Every time they released a new feature, they added a new optional parameter to their send_email endpoint. I started my function with three arguments: recipient, subject, and body. Six months later, I had a function definition that was twelve lines long, filled with template_id=None, tracking_pixel=False, and category="general". It was an eyesore, and every time the API updated, I had to manually update my function signature just to keep it from breaking.

That's when I stopped trying to predict the future and started using **kwargs. It allowed me to stop worrying about exactly which optional flags the API wanted and just let the function handle whatever the user decided to throw at it.

The Dictionary Behind the Double Asterisk

When you see **kwargs in a function definition, the ** is the actual operator doing the heavy lifting. It tells Python: "Take all the keyword arguments that weren't explicitly defined in the signature and pack them into a dictionary." By convention, we call this dictionary kwargs (short for keyword arguments), but you could call it **options or **stuff if you really wanted to. I don't recommend that, though—stick to kwargs so other developers know exactly what's happening.

def print_user_profile(name, **details):
    print(f"User: {name}")
    for key, value in details.items():
        print(f"- {key}: {value}")

# I can pass as many (or as few) extra details as I want
print_user_profile("Alice", location="New York", job="Engineer", hobby="Climbing")
print_user_profile("Bob", location="London")

In the example above, name is a required positional argument. But details becomes a dictionary containing whatever else I pass in. For Alice, details is {'location': 'New York', 'job': 'Engineer', 'hobby': 'Climbing'}. This is incredibly powerful when you don't know the exact set of inputs your function might receive.

Forwarding Arguments to Other Functions

The most common place you'll actually use this in a professional codebase is when you're writing a "wrapper" function. This is where one function calls another, but you want to allow the end-user to pass configurations straight through to the underlying tool without you having to redefine every single one of those options.

Imagine you're using a library to generate reports. The library's generate_pdf function has 50 different formatting options. You don't want to write all 50 in your own function, but you still want your users to be able to use them.

def my_custom_report(data, **formatting_options):
    print("Pre-processing data for the report...")
    # Do some logic here
    processed_data = data.upper() 
    
    # We just "unpack" the dictionary back into keyword arguments using **
    return external_library.generate_pdf(processed_data, **formatting_options)

By using **formatting_options in the signature and then **formatting_options in the call, you've created a transparent tunnel. The user passes margin=10, font="Arial" to my_custom_report, and those values slide right through to the generate_pdf function. It keeps your code clean and prevents your function signatures from growing into monsters.




📋 Practical Task

Exercise: Build a Dynamic Database Query Simulator

You are building a simulated database search tool. You want to create a function called search_database that requires a table_name, but should allow any number of optional filters (like status="active", min_age=21, or city="Seattle") to be passed in.

Requirements:

  • Create a function search_database(table_name, **filters).
  • The function should print: "Searching table: [table_name]".
  • If no filters are provided, it should print: "No filters applied. Returning all results.".
  • If filters are provided, it should loop through the filters dictionary and print each one in the format: "Filtering by [key]: [value]".

Test your code with these two calls:

search_database("users", status="active", city="Seattle")
search_database("products")
Rating
0 0

There are no comments for now.

to be the first to leave a comment.