If you’ve been learning Python for a while, you’ve probably seen a function defined like this:
def my_function(*args, **kwargs):
pass
At first glance, it looks confusing — what’s up with those asterisks before args and kwargs? Are they just Python magic?
Not quite.
In reality, *args and **kwargs are among the most powerful and flexible tools in Python. They allow you to pass a variable number of arguments to your functions, making your code adaptable and dynamic.
In this article, we’ll break down exactly how *args and **kwargs work, why they’re so useful, and how to use them effectively with real-world examples.
Let’s dive in! 🚀
Why You Need Flexible Function Arguments
Before understanding *args and **kwargs, let’s start with a simple scenario.
Imagine you’re building a function that sums up numbers:
def add(a, b):
return a + b
That works perfectly for two numbers:
print(add(2, 3)) # Output: 5
But what if you want to add three, four, or ten numbers?
Would you define separate functions for each case?
No — that would be repetitive and inefficient.
This is where *args and **kwargs come in. They let you handle any number of arguments dynamically, without changing the function definition each time.
What Is *args in Python?
The single asterisk (*) in *args allows a function to accept any number of positional arguments.
Think of it as packing all the extra positional arguments into a tuple.
Here’s a simple example:
def add_numbers(*args):
return sum(args)
print(add_numbers(1, 2)) # Output: 3
print(add_numbers(1, 2, 3, 4, 5)) # Output: 15
What’s Happening Here:
The *args parameter collects all the arguments passed to the function.
Inside the function, args acts like a tuple ((1, 2, 3, 4, 5) in the last example).
You can iterate over it, sum it, or manipulate it however you want.
Important Rule:
*args must always come after regular parameters in your function definition.
def greet(greeting, *names):
for name in names:
print(f"{greeting}, {name}!")
greet("Hello", "Alice", "Bob", "Charlie")
Output:
Hello, Alice!
Hello, Bob!
Hello, Charlie!
This flexibility makes your functions far more reusable.
What Is **kwargs in Python?
Now, what if you want to handle keyword arguments — that is, named arguments like age=25 or country="India"?
This is where the double asterisk (**) comes in.
**kwargs stands for keyword arguments, and it collects all the named arguments into a dictionary.
Example:
def show_details(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
show_details(name="Alice", age=25, country="India")
Output:
name: Alice
age: 25
country: India
What’s Happening Here:
**kwargs packs all keyword arguments into a dictionary ({'name': 'Alice', 'age': 25, 'country': 'India'}).
You can access them using keys, iterate over them, or modify them just like any dictionary.
Using Both *args and **kwargs Together
You can combine both in a single function to handle any number of positional and keyword arguments at once.
def display_info(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
display_info("Python", "Rocks", version=3.11, creator="Guido")
Output:
Positional arguments: ('Python', 'Rocks')
Keyword arguments: {'version': 3.11, 'creator': 'Guido'}
Order Matters:
When combining parameters, Python follows a specific order in the function definition:
def function(positional, *args, keyword_only, **kwargs):
pass
Positional parameters → come first
*args → captures extra positional arguments
Keyword-only parameters → must be named explicitly
**kwargs → captures all extra keyword arguments
This ensures clarity and avoids ambiguity in function calls.
How to Unpack *args and **kwargs
You can also unpack values from lists or dictionaries when calling a function.
This is known as argument unpacking.
Example with *args:
def multiply(a, b, c):
return a * b * c
numbers = [2, 3, 4]
print(multiply(*numbers)) # Output: 24
Here, the list [2, 3, 4] is unpacked into separate arguments a=2, b=3, c=4.
Example with **kwargs:
def introduce(name, age, country):
print(f"My name is {name}, I’m {age} years old from {country}.")
info = {'name': 'Alice', 'age': 25, 'country': 'India'}
introduce(**info)
Output:
My name is Alice, I’m 25 years old from India.
Why it’s useful:
When working with configurations, JSON data, or APIs, unpacking makes your code cleaner and easier to maintain.
Real-World Examples of *args and **kwargs
Let’s look at how these concepts appear in real-world Python use cases.
- Logging and Debugging Functions
Imagine writing a logging utility that takes any number of details:
def log_message(*args, **kwargs):
print("Message:", " ".join(map(str, args)))
for key, value in kwargs.items():
print(f"{key}: {value}")
log_message("Error", "404", "Not Found", user="John", time="10:45 PM")
Output:
Message: Error 404 Not Found
user: John
time: 10:45 PM
This makes your function flexible enough for any logging format.
- Passing Arguments Dynamically
You can use *args and **kwargs to pass parameters between functions dynamically.
def greet(name, age, city):
print(f"Hello {name}, age {age}, from {city}!")
def forward_greet(*args, **kwargs):
greet(*args, **kwargs)
forward_greet("Alice", 25, city="Mumbai")
This pattern is common in frameworks and libraries, where functions forward arguments without knowing their structure in advance.
- Working with Class Inheritance
In object-oriented Python, *args and **kwargs are often used in constructors (init) to pass parameters up the inheritance chain.
class Animal:
def init(self, species, **kwargs):
self.species = species
self.details = kwargs
class Dog(Animal):
def init(self, breed, **kwargs):
super().init(species="Dog", **kwargs)
self.breed = breed
dog = Dog(breed="Labrador", color="Golden", age=3)
print(dog.species, dog.breed, dog.details)
Output:
Dog Labrador {'color': 'Golden', 'age': 3}
Here, **kwargs makes the class constructors flexible and extendable.
- Building Configurable APIs
Developers often design APIs or functions that accept many optional parameters. Using **kwargs, you can keep your function flexible:
def send_email(to, subject, **options):
email = {
"to": to,
"subject": subject,
"cc": options.get("cc", []),
"bcc": options.get("bcc", []),
"attachments": options.get("attachments", []),
}
print("Email configuration:", email)
send_email("john@example.com", "Project Update", cc=["team@example.com"], attachments=["file.pdf"])
This approach keeps your API both powerful and easy to extend later.
Best Practices for Using *args and **kwargs
While these tools are incredibly flexible, misuse can make your code confusing.
Here are a few best practices to follow:
✅ 1. Use descriptive argument names
Avoid naming every variable args or kwargs. Use context-specific names when possible.
def calculate_total(*prices):
return sum(prices)
✅ 2. Avoid overusing them
If your function can be written with fixed parameters, don’t use *args or **kwargs unnecessarily. It can make debugging harder.
✅ 3. Document your functions
Always document what kind of arguments your function expects, especially when using flexible parameters.
def register_user(*args, **kwargs):
"""
Accepts positional arguments for user details
and keyword arguments for optional settings.
"""
✅ 4. Combine with default arguments wisely
You can mix standard arguments with flexible ones, but ensure order and readability are maintained.
Common Mistakes and How to Avoid Them
Here are a few pitfalls developers often run into:
❌ Forgetting the order of parameters
def example(a, **kwargs, *args): # Wrong order
pass
✅ Correct order:
def example(a, *args, **kwargs):
pass
❌ Modifying args directly
Since args is a tuple, it’s immutable. If you need to modify it, convert it to a list first.
args = list(args)
❌ Shadowing variable names
Don’t use args or kwargs as parameter names in nested functions — it can cause confusion or override parent scope variables.
When to Use *args and **kwargs
You should consider using these when:
You don’t know beforehand how many arguments a function will receive.
You’re writing utility functions, decorators, or API wrappers.
You’re working with inheritance where subclass constructors need flexibility.
You want your function to adapt easily to future changes.
In short — when flexibility matters more than strict structure.
Conclusion
*args and **kwargs might look intimidating at first, but once you understand how they work, they become your best friends for building flexible, reusable Python functions.
They let you:
Pass any number of positional and keyword arguments.
Unpack data cleanly from lists or dictionaries.
Build extensible APIs and scalable codebases.
So the next time you see a function with *args or **kwargs, you’ll know — it’s not Python magic. It’s Python’s elegance in making your functions dynamic and future-proof.
Final takeaway:
Experiment with both *args and **kwargs in your own projects. You’ll quickly see how they help you write cleaner, more adaptable, and professional-grade Python code.
Top comments (0)