🕯️ Magic Note
The names args and kwargs are conventions. The real syntax is the asterisk * and double asterisk **. You could name them *numbers and **options. Python only cares about the stars. But follow the convention. Other Python programmers expect *args and **kwargs.
Python
# Sum any number of arguments
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20)) # 30
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0
Python
# First parameter is required, then any number of extras
def greet(greeting, *names):
for name in names:
print(f”{greeting}, {name}!”)
greet(“Hello”, “Ali”, “Sara”, “Reza”)
# Hello, Ali!
# Hello, Sara!
# Hello, Reza!
def multiply(multiplier, *numbers):
return [num * multiplier for num in numbers]
print(multiply(3, 1, 2, 3, 4, 5)) # [3, 6, 9, 12, 15]
Python
# Print any keyword arguments passed
def print_info(**kwargs):
for key, value in kwargs.items():
print(f”{key}: {value}”)
print_info(name=”Ali”, age=25, city=”Tehran”)
# name: Ali
# age: 25
# city: Tehran
def create_user(username, **details):
user = {“username”: username}
user.update(details)
return user
user = create_user(“ali123″, age=25, city=”Tehran”, is_active=True)
print(user) # {‘username’: ‘ali123’, ‘age’: 25, ‘city’: ‘Tehran’, ‘is_active’: True}
🕯️ Magic Note
The keys in **kwargs must be valid Python identifiers (no spaces, cannot start with a number). They become keys in a dictionary. This is perfect for optional configuration options.
Python
def flexible_function(a, b=10, *args, **kwargs):
print(f”a: {a}”)
print(f”b: {b}”)
print(f”args: {args}”)
print(f”kwargs: {kwargs}”)
flexible_function(1, 20, 30, 40, 50, name=”Ali”, age=25)
# a: 1
# b: 20
# args: (30, 40, 50)
# kwargs: {‘name’: ‘Ali’, ‘age’: 25}
Python
# Unpacking a list into positional arguments
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
result = add(*numbers) # Equivalent to add(1, 2, 3)
print(result) # 6
# Unpacking a dictionary into keyword arguments
def display(name, age, city):
print(f”{name} is {age} from {city}”)
person = {“name”: “Ali”, “age”: 25, “city”: “Tehran”}
display(**person) # Equivalent to display(name=”Ali”, age=25, city=”Tehran”)
# Mixing unpacking with regular arguments
def greet(greeting, name):
print(f”{greeting}, {name}!”)
data = [“Hello”, “Ali”]
greet(*data) # Hello, Ali!
🕯️ Magic Note
Unpacking with * and ** in function calls is the mirror image of collecting with *args and **kwargs in function definitions. The same syntax does opposite things depending on context.
Python
from datetime import datetime
def log_message(level, *messages, **options):
“””
Log messages with a level and optional settings.
Parameters:
level (str): Log level (INFO, WARNING, ERROR)
*messages: Variable number of message strings
**options: Optional settings like write_to_file, show_timestamp
“””
show_time = options.get(“show_timestamp”, True)
write_file = options.get(“write_to_file”, False)
if show_time:
timestamp = datetime.now().strftime(“%Y-%m-%d %H:%M:%S”)
prefix = f”[{timestamp}] [{level}]”
else:
prefix = f”[{level}]”
for msg in messages:
log_line = f”{prefix} {msg}”
print(log_line)
if write_file:
with open(“app.log”, “a”) as f:
f.write(log_line + “\n”)
# Usage examples
log_message(“INFO”, “Application started”)
log_message(“WARNING”, “Low disk space”, “Cleaning cache”, write_to_file=True)
log_message(“ERROR”, “Connection failed”, show_timestamp=False)
log_message(“DEBUG”, “Debug 1”, “Debug 2”, “Debug 3”, write_to_file=True, show_timestamp=True)
Python
import time
def timer(func):
“””Decorator that times how long a function takes to run.”””
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # Pass all arguments through
end = time.time()
print(f”{func.__name__} took {end – start:.4f} seconds”)
return result
return wrapper
@timer
def slow_function(a, b, c=1):
time.sleep(1)
return a + b + c
@timer
def another_function(name, *friends):
time.sleep(0.5)
return f”{name} has friends: {friends}”
# The same decorator works on functions with different signatures
print(slow_function(5, 10, c=20))
print(another_function(“Ali”, “Sara”, “Reza”))
🕯️ Magic Note
Decorators are one of the most powerful uses of *args and **kwargs. They allow you to wrap any function without knowing its parameters. You will learn more about decorators in Lesson 46.
Python
class Animal:
def __init__(self, name, species, **kwargs):
self.name = name
self.species = species
for key, value in kwargs.items():
setattr(self, key, value)
class Dog(Animal):
def __init__(self, name, **kwargs):
super().__init__(name, species=”dog”, **kwargs)
class Cat(Animal):
def __init__(self, name, **kwargs):
super().__init__(name, species=”cat”, **kwargs)
# Each subclass can accept extra attributes without redefining them
buddy = Dog(“Buddy”, age=5, color=”brown”, owner=”Ali”)
print(buddy.age, buddy.color, buddy.owner) # 5 brown Ali
whiskers = Cat(“Whiskers”, age=3, favorite_toy=”yarn”)
print(whiskers.age, whiskers.favorite_toy) # 3 yarn
- Do not use *args if the number of arguments is fixed and small. Be explicit.
- Do not use **kwargs as a way to hide parameters. Document expected keys.
- Do not unpack *args into a function that expects specific types without checking.
- Do not overuse. Functions with too many *args and **kwargs can be hard to understand.
Python
# Bad: Unnecessary use of *args
def add(*args): # This works but is unclear
if len(args) != 2:
raise ValueError(“Expected exactly 2 arguments”)
return args[0] + args[1]
# Good: Explicit parameters
def add(a, b): # Clear, self-documenting
return a + b
- Forgetting the asterisk: def func(args) is a single parameter named args, not variable arguments
- Putting **kwargs before *args (SyntaxError)
- Trying to use *args and **kwargs multiple times in one function
- Not knowing that args is a tuple (immutable)
- Mutating **kwargs inside the function (it is a regular dictionary, so you can, but be careful)
- Confusing unpacking in function calls with collecting in function definitions
Python
# Common mistake: Missing asterisk
def bad_sum(args): # This takes ONE argument named args
return sum(args)
# Usage would be: bad_sum([1, 2, 3]) – not multiple arguments
# Correct: With asterisk
def good_sum(*args): # This takes any number of arguments
return sum(args)
# Usage: good_sum(1, 2, 3)
- What is the difference between *args and **kwargs?
- Write a function that accepts any number of arguments and returns their average.
- How do you unpack a list into positional arguments when calling a function?
- What is the correct order of parameters in a function that uses *args and **kwargs?
- Write a function that accepts a required name, any number of friends, and any number of extra info as keyword arguments.
- Why do decorators often use *args and **kwargs?
⚡ Whisper
Two stars, one star. They look like magic symbols. And in a way, they are. The single star opens a door to many values. It gathers them into a tuple, a quiet line of waiting arguments. The double star opens another door. It gathers named values into a dictionary, pairs of keys and secrets. Together they make your functions boundless. They can accept anything the caller throws at them. But with great power comes great responsibility. Use these stars when you truly need flexibility. When you are wrapping another function. When you are passing through to a parent class. When the number of arguments is truly unknown. But for everyday functions, be explicit. Name your parameters. Let your function signature tell a story. A balance between flexibility and clarity is the mark of a master. Too many stars and the sky becomes confusing. Too few and the sky feels empty. Find your balance.