Defining Functions
Checking access...
Functions are reusable blocks of code that take inputs, perform work, and return outputs.
Basic Function Syntax
def greet(name): """Say hello to someone.""" return f"Hello, {name}!"
# Call the functionmessage = greet("Alice")print(message) # Hello, Alice!Parameters and Arguments
# Positional parametersdef add(a, b): return a + b
add(3, 5) # 8
# Default parametersdef power(base, exponent=2): return base ** exponent
power(4) # 16 (4²)power(4, 3) # 64 (4³)
# Keyword argumentsdef describe(name, age, role): return f"{name} is {age}, works as {role}"
describe(name="Alice", role="engineer", age=25)# Order doesn't matter with keywords
# Required vs optionaldef connect(host, port=5432, timeout=30): """host is required; port and timeout have defaults.""" passCaution
Default parameter values are evaluated once at function definition time. Never use mutable defaults like [] or {}.
Mutable Default Pitfall
# BAD — all calls share the same listdef add_item(item, items=[]): items.append(item) return items
print(add_item(1)) # [1]print(add_item(2)) # [1, 2] — reused the same list!
# GOOD — use None and create a new list each calldef add_item(item, items=None): if items is None: items = [] items.append(item) return itemsReturn Values
# Single return valuedef square(n): return n ** 2
# Multiple return values (as tuple)def min_max(items): return min(items), max(items)
low, high = min_max([3, 1, 4, 1, 5])print(low, high) # 1 5
# Early returndef divide(a, b): if b == 0: return None # Early exit return a / b
# Implicit Nonedef log(message): print(f"[LOG]: {message}") # No return statement — returns NoneDocstrings
def calculate_bmi(weight_kg, height_m): """Calculate Body Mass Index.
Args: weight_kg: Weight in kilograms height_m: Height in meters
Returns: BMI as a float, or None if invalid input """ if weight_kg <= 0 or height_m <= 0: return None return weight_kg / (height_m ** 2)
# Access docstringprint(calculate_bmi.__doc__)help(calculate_bmi)Scope and Variables
global_var = "I'm global"
def scope_demo(): local_var = "I'm local" print(global_var) # Accessible # print(local_var) # Only accessible inside function
scope_demo()# print(local_var) # NameError!
# Modifying global variablescounter = 0
def increment(): global counter # Must declare global to modify counter += 1
increment()print(counter) # 1
# Enclosing scope (nested functions)def outer(): x = "outer"
def inner(): nonlocal x # Modify enclosing scope variable x = "inner" print(x)
inner() print(x) # "inner" — modified by inner()
outer()Type Hints for Functions
def greet(name: str, greeting: str = "Hello") -> str: """Type-annotated function.""" return f"{greeting}, {name}!"
# Type hints are documentation — not enforced at runtimegreet("Alice") # OKgreet(42) # Also runs fine — no runtime check
# More complex typesfrom typing import List, Optional, Dict, Tuple
def process(items: List[int]) -> Dict[str, Optional[int]]: return {"min": min(items), "max": max(items)}Key Takeaways
- Functions use
def, parameters, andreturn - Default parameters are evaluated once — avoid mutable defaults
- Use
Noneas default for mutable parameters, create new inside function - Functions can return multiple values as tuples
globalandnonlocalmodify outer scope variables- Docstrings with
"""document function behavior — accessible viahelp() - Type hints document expected types but aren’t enforced