Functions & Scope
*args and **kwargs
Flexible Arguments
You're writing a logging function that should accept any number of messages.
Or a config function that takes arbitrary key-value options. *args and
**kwargs let you accept variable arguments without knowing them in advance.
Accept any positional arguments
Use *args to collect extra positional arguments.
def main():
print("=== *args: Variable Positional Arguments ===\n")
# Any number of arguments
print("sum_all with different counts:")
print(f"sum_all(1, 2) = {sum_all(1, 2)}")
print(f"sum_all(1, 2, 3) = {sum_all(1, 2, 3)}")
print(f"sum_all(1, 2, 3, 4, 5) = {sum_all(1, 2, 3, 4, 5)}")
print(f"sum_all() = {sum_all()}") # Zero args OK!
print("\n=== What is *args? ===")
show_args(10, 20, 30)
print("\n=== Required + *args ===")
greet("Hello", "Alice", "Bob", "Charlie")
greet("Hi", "Dave")
print("\n=== Type Hints with *args ===")
result = concat_strings("Hello", " ", "World", "!")
print(f"concat_strings result: {result}")
def sum_all(*numbers):
"""Sum any number of values."""
total = 0
for n in numbers:
total += n
return total
def show_args(*args):
"""Show what args contains."""
print(f"args = {args}")
print(f"type(args) = {type(args).__name__}")
print(f"len(args) = {len(args)}")
def greet(greeting, *names):
"""Greet multiple people."""
for name in names:
print(f"{greeting}, {name}!")
def concat_strings(*strings: str) -> str:
"""Concatenate any number of strings (typed)."""
return "".join(strings)
if __name__ == "__main__":
main()
*args collects extra positional arguments into a tuple.
Accept any keyword arguments
Use **kwargs to collect extra keyword arguments.
def main():
print("=== **kwargs: Variable Keyword Arguments ===\n")
# Any keyword arguments
print("User profiles:")
print_info(name="Alice", age=30, city="NYC")
print()
print_info(name="Bob", occupation="Developer")
print()
print_info(username="charlie", email="c@test.com", premium=True)
print("\n=== What is **kwargs? ===")
show_kwargs(x=10, y=20, z=30)
print("\n=== Building Objects ===")
config = make_config(debug=True, timeout=30, retries=3)
print(f"Config: {config}")
print("\n=== With Validation ===")
try:
validated = create_user(name="Alice", email="alice@test.com")
print(f"Created: {validated}")
except ValueError as e:
print(f"Error: {e}")
def print_info(**kwargs):
"""Print all provided info."""
for key, value in kwargs.items():
print(f" {key}: {value}")
def show_kwargs(**kwargs):
"""Show what kwargs contains."""
print(f"kwargs = {kwargs}")
print(f"type(kwargs) = {type(kwargs).__name__}")
print(f"keys = {list(kwargs.keys())}")
def make_config(**settings):
"""Create config dict from keyword args."""
# Add defaults
config = {
'debug': False,
'timeout': 60,
'retries': 5
}
# Override with provided settings
config.update(settings)
return config
def create_user(**fields):
"""Create user with validation."""
required = ['name', 'email']
for field in required:
if field not in fields:
raise ValueError(f"Missing required field: {field}")
return fields
if __name__ == "__main__":
main()
**kwargs collects extra keyword arguments into a dictionary.
Combine *args and **kwargs
Accept both kinds of variable arguments.
def main():
print("=== Combining *args and **kwargs ===\n")
# Both together
log("INFO", "User logged in", user="Alice", ip="192.168.1.1")
log("ERROR", "Connection failed", "timeout", retries=3)
log("DEBUG", "Processing") # Just message
print("\n=== Full Signature Order ===")
demonstrate_order("required", "extra1", "extra2",
kw_only="value", opt1="a", opt2="b")
print("\n=== Catch-All Function ===")
catch_all(1, 2, 3, name="test", flag=True)
def log(level, message, *tags, **metadata):
"""Log with optional tags and metadata."""
print(f"[{level}] {message}")
if tags:
print(f" Tags: {', '.join(tags)}")
if metadata:
for key, value in metadata.items():
print(f" {key}: {value}")
print()
def demonstrate_order(required, *args, kw_only, **kwargs):
"""Show the required parameter order."""
print(f"required: {required}")
print(f"*args: {args}")
print(f"kw_only: {kw_only}")
print(f"**kwargs: {kwargs}")
def catch_all(*args, **kwargs):
"""Accept literally anything."""
print(f"Positional ({len(args)}): {args}")
print(f"Keyword ({len(kwargs)}): {kwargs}")
if __name__ == "__main__":
main()
Order matters: regular params, *args, keyword-only params, **kwargs.
Unpack into function calls
Use * and ** to expand sequences and dicts into arguments.
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
def main():
print("=== Unpacking: * and ** in Function Calls ===\n")
# Unpack list/tuple into positional args
numbers =
print(f"numbers = {numbers}")
print(f"add(numbers) would fail - it's one arg")
print(f"add(*numbers) = {add(*numbers)}") # Unpacks!
print("\n=== Unpack Dict into Keyword Args ===")
config = {'host': 'localhost', 'port': 8080, 'debug': True}
print(f"config = {config}")
connect(**config) # Same as connect(host='...', port=..., debug=...)
print("\n=== Combined Unpacking ===")
args = [5, 3]
kwargs = {'operation': 'multiply'}
calculate(*args, **kwargs)
print("\n=== Range Example ===")
# start, stop, step
bounds =
print(f"bounds = {bounds}")
print(f"list(range(*bounds)) = {list(range(*bounds))}")
print("\n=== Merge Dicts with ** ===")
defaults = {'color': 'blue', 'size': 'medium'}
overrides = {'size': 'large', 'style': 'bold'}
merged = {**defaults, **overrides}
print(f"defaults: {defaults}")
print(f"overrides: {overrides}")
print(f"merged: {merged}")
def add(a, b, c):
"""Add three numbers."""
return a + b + c
def connect(host, port, debug=False):
"""Simulate connection."""
print(f"Connecting to {host}:{port} (debug={debug})")
def calculate(x, y, operation='add'):
"""Perform calculation."""
if operation == 'add':
print(f"{x} + {y} = {x + y}")
elif operation == 'multiply':
print(f"{x} * {y} = {x * y}")
if __name__ == "__main__":
main()
func(*list) unpacks list as positional args. func(**dict) unpacks as keyword args.
Forward arguments to other functions
Pass received arguments to another function unchanged.
def main():
print("=== Forwarding Arguments ===\n")
# Wrapper forwards to inner function
print("Wrapper forwarding:")
wrapper_example(1, 2, 3, name="test", flag=True)
print("\n=== Logging Wrapper ===")
logged_add(10, 20)
logged_multiply(5, 3, verbose=True)
print("\n=== Super() Forwarding ===")
fancy = FancyButton("Click Me", color="blue", size="large")
def wrapper_example(*args, **kwargs):
"""Wrapper that forwards everything."""
print(f" Wrapper received: args={args}, kwargs={kwargs}")
print(" Forwarding to inner function...")
inner_function(*args, **kwargs)
def inner_function(*args, **kwargs):
print(f" Inner got: args={args}, kwargs={kwargs}")
# Logging wrapper pattern
def logged_add(a, b):
print(f" [LOG] Calling add({a}, {b})")
result = a + b
print(f" [LOG] Result: {result}")
return result
def logged_multiply(a, b, verbose=False):
if verbose:
print(f" [VERBOSE] multiply called with a={a}, b={b}")
result = a * b
print(f" [LOG] multiply result: {result}")
return result
# Class inheritance forwarding
class Button:
def __init__(self, text, **options):
self.text = text
self.options = options
print(f" Button created: '{text}' with {options}")
class FancyButton(Button):
def __init__(self, text, **options):
# Add default options
options.setdefault('border', True)
# Forward to parent
super().__init__(text, **options)
print(" (FancyButton enhancements applied)")
if __name__ == "__main__":
main()
Wrapper functions use *args, **kwargs to forward all arguments.
Exercise: decorators.py
Use *args/**kwargs in decorator functions