Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
69: Variable-Length Keyword Arguments with **kwargs
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
filtersdictionary 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")
There are no comments for now.