1️⃣ Why *args and **kwargs exist
Problem:
You don’t always know how many arguments a function will receive.
Solution:
*args- Collects extra positional arguments
- Store in touple
- Normal args must come before *args
**kwargs- Collects extra keyword arguments
- Store in dictionary
- normal arg → *args → **kwargs
2️⃣ *args (positional arguments)
def show(*args):
print(type(args)) # <class 'tuple'>
print(args) # (1, 2, 3)
show(1, 2, 3)
### 🔹 Mixing normal args and `*args`
def demo(a, b, *args):
print(a) # 1
print(b) # 2
print(args) # (3, 4, 5)
demo(1, 2, 3, 4, 5)3️⃣ **kwargs (keyword arguments)
def show(**kwargs):
print(kwargs)
show(a=1, b=2) # {'a': 1, 'b': 2}
def mix(a, b, *args, **kwargs):
print(a, b) # 1 2
print(args) # (3, 4)
print(kwargs) # {'x': 10, 'y': 20}
mix(1, 2, 3, 4, x=10, y=20)4️⃣ Unpacking arguments with * & ** (reverse of collecting)
# Unpacking list/tuple using
nums = [1, 2, 3]
print(*nums) # 1 2 3
# Unpacking dict
data = {"a": 1, "b": 2}
def show(a, b):
print(a, b) # 1 2
show(**data)
# Forwarding arguments (very important)
Used heavily in decorators, wrappers, frameworks.
def wrapper(*args, **kwargs):
return original(*args, **kwargs)