Aller au contenu principal

04.2 - *args, **kwargs & Closures

Theory 25 min Intermediate

*args — Variable Positional Arguments

*args collects any number of positional arguments into a tuple.

def add(*args):
return sum(args)

add(1, 2, 3) # 6
add(10, 20) # 30
add(1, 2, 3, 4, 5) # 15

# *args is just a convention — the * is what matters
def display(*values):
for v in values:
print(v)

display("a", "b", "c")
# a
# b
# c

**kwargs — Variable Keyword Arguments

**kwargs collects any number of keyword arguments into a dict.

def display_info(**kwargs):
for key, value in kwargs.items():
print(f" {key}: {value}")

display_info(name="Alice", age=30, city="Montreal")
# name: Alice
# age: 30
# city: Montreal

# Practical use: flexible config builder
def create_config(env, **options):
config = {"environment": env}
config.update(options)
return config

cfg = create_config("prod", debug=False, workers=4, max_connections=100)

Combining All Parameter Types

The order of parameters is strict:

def func(positional, /, regular, *args, keyword_only, **kwargs)
│ │ └── keyword-only (after *)
│ └── variadic positional
└── positional-only (Python 3.8+)
def mixed(a, b, *args, key1="default", key2=None, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"key1={key1}, key2={key2}")
print(f"kwargs={kwargs}")

mixed(1, 2, 3, 4, key1="hello", extra="value")
# a=1, b=2
# args=(3, 4)
# key1=hello, key2=None
# kwargs={'extra': 'value'}

Unpacking Arguments with * and **

Use * to unpack a list/tuple as positional arguments, ** to unpack a dict as keyword arguments:

def power(base, exponent):
return base ** exponent

args = (2, 10)
power(*args) # 2 ** 10 = 1024 (unpacks tuple)

kwargs = {"base": 3, "exponent": 4}
power(**kwargs) # 3 ** 4 = 81 (unpacks dict)

# Combine both
params = (2,)
options = {"exponent": 8}
power(*params, **options) # 256

# Useful for forwarding arguments
def wrapper(*args, **kwargs):
print("Before call")
result = some_function(*args, **kwargs)
print("After call")
return result

Positional-Only Parameters (Python 3.8+)

Parameters before / can only be passed positionally — not by name:

def divide(numerator, denominator, /):
return numerator / denominator

divide(10, 2) # OK: 5.0
divide(10, denominator=2) # TypeError!

# Use case: match built-in behavior
len("hello") # not len(obj="hello")

Keyword-Only Parameters

Parameters after * can only be passed by keyword:

def connect(host, port, *, ssl=True, timeout=30):
print(f"Connecting to {host}:{port} ssl={ssl}")

connect("db.com", 5432) # OK
connect("db.com", 5432, ssl=False) # OK
connect("db.com", 5432, False) # TypeError! ssl must be keyword

Closures — Functions that Remember

A closure is a function that captures variables from its enclosing scope even after that scope has finished.

def make_multiplier(factor):
"""Returns a function that multiplies by factor."""
def multiply(n):
return n * factor # factor is captured from enclosing scope
return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

double(5) # 10
triple(5) # 15
double(8) # 16

Practical Closure Patterns

# Counter factory
def make_counter(start=0):
count = [start] # using list for mutability in Python 2 compat
def increment(step=1):
count[0] += step
return count[0]
return increment

counter = make_counter()
counter() # 1
counter() # 2
counter(5) # 7

# Memoization with closure
def make_cached(func):
cache = {}
def cached(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return cached

@make_cached
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)

# Partial application
def make_adder(n):
return lambda x: x + n

add5 = make_adder(5)
add5(3) # 8
add5(10) # 15

functools.partial

functools.partial is the standard library way to create partial functions:

from functools import partial

def power(base, exponent):
return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

square(5) # 25
cube(3) # 27

# Useful with map/filter
from functools import partial
double = partial(lambda x, n: x * n, n=2)
list(map(double, [1, 2, 3, 4])) # [2, 4, 6, 8]

Key Vocabulary

TermDefinition
*argsCollects extra positional arguments into a tuple
**kwargsCollects extra keyword arguments into a dict
Unpacking*list or **dict to pass iterable as arguments
ClosureFunction that captures and remembers variables from its enclosing scope
Enclosing scopeThe scope of the outer function in a nested function
Positional-onlyParameters before / that cannot be passed by name
Keyword-onlyParameters after * that must be passed by name
functools.partialCreates a new function with some arguments pre-filled

Summary

  • *args collects unlimited positional arguments as a tuple
  • **kwargs collects unlimited keyword arguments as a dict
  • Argument order in definitions: positional → *args → keyword-only → **kwargs
  • Use *list and **dict to unpack when calling functions
  • Closures capture variables from their enclosing scope — useful for factory functions and state
  • functools.partial pre-fills arguments to create specialized versions of functions