python-by-example/05_functions/02_arguments.py
2025-12-30 08:50:00 +02:00

309 lines
10 KiB
Python

"""
================================================================================
File: 02_arguments.py
Topic: Function Arguments in Python
================================================================================
This file demonstrates the different ways to pass arguments to functions
in Python. Understanding these patterns is essential for writing flexible
and reusable functions.
Key Concepts:
- Positional arguments
- Keyword arguments
- Default parameter values
- *args (variable positional arguments)
- **kwargs (variable keyword arguments)
- Argument unpacking
- Keyword-only and positional-only arguments
================================================================================
"""
# -----------------------------------------------------------------------------
# 1. Positional Arguments
# -----------------------------------------------------------------------------
# Arguments passed in order; position matters
print("--- Positional Arguments ---")
def greet(first_name, last_name):
"""Greet using positional arguments."""
print(f"Hello, {first_name} {last_name}!")
greet("John", "Doe") # Correct order
greet("Jane", "Smith") # Correct order
# Order matters!
greet("Doe", "John") # Wrong order gives wrong result
# -----------------------------------------------------------------------------
# 2. Keyword Arguments
# -----------------------------------------------------------------------------
# Arguments passed by name; order doesn't matter
print("\n--- Keyword Arguments ---")
def create_profile(name, age, city):
"""Create a profile using keyword arguments."""
print(f"Name: {name}, Age: {age}, City: {city}")
# Using keyword arguments (order doesn't matter)
create_profile(name="Alice", age=25, city="NYC")
create_profile(city="Tokyo", name="Bob", age=30)
# Mix positional and keyword (positional must come first!)
create_profile("Charlie", city="London", age=28)
# -----------------------------------------------------------------------------
# 3. Default Parameter Values
# -----------------------------------------------------------------------------
# Parameters can have default values
print("\n--- Default Parameter Values ---")
def greet_with_title(name, title="Mr."):
"""Greet with an optional title."""
print(f"Hello, {title} {name}!")
greet_with_title("Smith") # Uses default title
greet_with_title("Johnson", "Dr.") # Overrides default
# Multiple defaults
def create_user(username, email, role="user", active=True):
"""Create a user with defaults."""
print(f"User: {username}, Email: {email}, Role: {role}, Active: {active}")
create_user("john", "john@example.com")
create_user("admin", "admin@example.com", role="admin")
create_user("test", "test@example.com", active=False)
# CAUTION: Mutable default arguments
# DON'T DO THIS:
def bad_function(items=[]): # Bad! List is shared across calls
items.append(1)
return items
# DO THIS INSTEAD:
def good_function(items=None):
"""Safe way to handle mutable defaults."""
if items is None:
items = []
items.append(1)
return items
print(f"\nBad function calls: {bad_function()}, {bad_function()}, {bad_function()}")
print(f"Good function calls: {good_function()}, {good_function()}, {good_function()}")
# -----------------------------------------------------------------------------
# 4. *args - Variable Positional Arguments
# -----------------------------------------------------------------------------
# Accept any number of positional arguments
print("\n--- *args (Variable Positional Arguments) ---")
def sum_all(*args):
"""Sum all provided numbers."""
print(f" Received args: {args} (type: {type(args).__name__})")
return sum(args)
print(f"sum_all(1, 2): {sum_all(1, 2)}")
print(f"sum_all(1, 2, 3, 4, 5): {sum_all(1, 2, 3, 4, 5)}")
print(f"sum_all(): {sum_all()}")
# Combining regular parameters with *args
def greet_people(greeting, *names):
"""Greet multiple people with the same greeting."""
for name in names:
print(f" {greeting}, {name}!")
print("\nGreeting people:")
greet_people("Hello", "Alice", "Bob", "Charlie")
# -----------------------------------------------------------------------------
# 5. **kwargs - Variable Keyword Arguments
# -----------------------------------------------------------------------------
# Accept any number of keyword arguments
print("\n--- **kwargs (Variable Keyword Arguments) ---")
def print_info(**kwargs):
"""Print all keyword arguments."""
print(f" Received kwargs: {kwargs} (type: {type(kwargs).__name__})")
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Baraa", age=25, city="Gaza")
print_info(language="Python", level="Expert")
# Combining all parameter types
def complete_example(required, *args, default="value", **kwargs):
"""Demonstrate all parameter types."""
print(f" Required: {required}")
print(f" *args: {args}")
print(f" Default: {default}")
print(f" **kwargs: {kwargs}")
print("\nComplete example:")
complete_example("must_have", 1, 2, 3, default="custom", extra="data", more="stuff")
# -----------------------------------------------------------------------------
# 6. Argument Unpacking
# -----------------------------------------------------------------------------
# Use * and ** to unpack sequences and dictionaries into arguments
print("\n--- Argument Unpacking ---")
def introduce(name, age, city):
"""Introduce a person."""
print(f"I'm {name}, {age} years old, from {city}")
# Unpack list/tuple with *
person_list = ["Alice", 30, "Paris"]
introduce(*person_list) # Same as: introduce("Alice", 30, "Paris")
# Unpack dictionary with **
person_dict = {"name": "Bob", "age": 25, "city": "Berlin"}
introduce(**person_dict) # Same as: introduce(name="Bob", age=25, city="Berlin")
# Combine unpacking
def multiply(a, b, c):
return a * b * c
nums = [2, 3]
print(f"\nMultiply with unpacking: {multiply(*nums, 4)}")
# -----------------------------------------------------------------------------
# 7. Keyword-Only Arguments (Python 3+)
# -----------------------------------------------------------------------------
# Arguments after * must be passed as keywords
print("\n--- Keyword-Only Arguments ---")
def format_name(first, last, *, upper=False, reverse=False):
"""
Format a name. 'upper' and 'reverse' are keyword-only.
"""
name = f"{first} {last}"
if reverse:
name = f"{last}, {first}"
if upper:
name = name.upper()
return name
# These work:
print(format_name("John", "Doe"))
print(format_name("John", "Doe", upper=True))
print(format_name("John", "Doe", reverse=True, upper=True))
# This would fail:
# format_name("John", "Doe", True) # Error! upper must be keyword
# Using bare * for keyword-only
def connect(*, host, port, timeout=30):
"""All parameters are keyword-only."""
print(f" Connecting to {host}:{port} (timeout: {timeout}s)")
connect(host="localhost", port=8080)
connect(host="db.example.com", port=5432, timeout=60)
# -----------------------------------------------------------------------------
# 8. Positional-Only Arguments (Python 3.8+)
# -----------------------------------------------------------------------------
# Arguments before / must be passed positionally
print("\n--- Positional-Only Arguments ---")
def divide(x, y, /):
"""x and y must be passed positionally."""
return x / y
print(f"divide(10, 2): {divide(10, 2)}")
# divide(x=10, y=2) # Error! x and y are positional-only
# Combining positional-only, regular, and keyword-only
def complex_function(pos_only1, pos_only2, /, regular1, regular2, *, kw_only1, kw_only2="default"):
"""
pos_only1, pos_only2: positional-only (before /)
regular1, regular2: can be either
kw_only1, kw_only2: keyword-only (after *)
"""
print(f" pos_only: {pos_only1}, {pos_only2}")
print(f" regular: {regular1}, {regular2}")
print(f" kw_only: {kw_only1}, {kw_only2}")
print("\nComplex function:")
complex_function(1, 2, 3, regular2=4, kw_only1="required")
# -----------------------------------------------------------------------------
# 9. Type Hints for Arguments (Preview)
# -----------------------------------------------------------------------------
# Adding type information (doesn't enforce, just hints)
print("\n--- Type Hints ---")
def calculate_total(price: float, quantity: int, tax_rate: float = 0.1) -> float:
"""
Calculate total price with tax.
Args:
price: Unit price (float)
quantity: Number of items (int)
tax_rate: Tax rate as decimal (default 0.1 = 10%)
Returns:
Total price including tax
"""
subtotal = price * quantity
return subtotal * (1 + tax_rate)
total = calculate_total(19.99, 3)
print(f"Total: ${total:.2f}")
# Type hints with complex types
from typing import List, Optional, Dict
def process_scores(scores: List[int], name: Optional[str] = None) -> Dict[str, float]:
"""Process a list of scores and return statistics."""
return {
"name": name or "Unknown",
"average": sum(scores) / len(scores),
"highest": max(scores),
"lowest": min(scores)
}
result = process_scores([85, 90, 78, 92], "Alice")
print(f"Score stats: {result}")
# -----------------------------------------------------------------------------
# 10. Practical Examples
# -----------------------------------------------------------------------------
print("\n--- Practical Examples ---")
# Flexible logging function
def log(message, *tags, level="INFO", **metadata):
"""Log a message with optional tags and metadata."""
tag_str = " ".join(f"[{tag}]" for tag in tags)
meta_str = " | ".join(f"{k}={v}" for k, v in metadata.items())
output = f"[{level}] {tag_str} {message}"
if meta_str:
output += f" ({meta_str})"
print(output)
log("Server started", "server", "startup")
log("User logged in", "auth", level="DEBUG", user_id=123, ip="192.168.1.1")
# Builder pattern with kwargs
def create_html_element(tag, content="", **attributes):
"""Create an HTML element string."""
attrs = " ".join(f'{k}="{v}"' for k, v in attributes.items())
if attrs:
attrs = " " + attrs
return f"<{tag}{attrs}>{content}</{tag}>"
print("\n" + create_html_element("p", "Hello World"))
print(create_html_element("a", "Click me", href="https://example.com", target="_blank"))
print(create_html_element("input", type="text", placeholder="Enter name", id="name_input"))