Docs
README
05 - Functions
📌 What You'll Learn
- •Function definition and calling
- •Parameters and arguments
- •Return values
- •Default arguments
- •
*argsand**kwargs - •Lambda functions
- •Recursion
- •Introduction to decorators
🔧 What is a Function?
A function is a reusable block of code that performs a specific task. Functions help you:
- •Avoid code repetition (DRY - Don't Repeat Yourself)
- •Organize code into logical units
- •Make code easier to read and maintain
- •Enable code reuse
📝 Function Definition and Calling
Basic Syntax
# Define a function
def greet():
print("Hello, World!")
# Call the function
greet() # Output: Hello, World!
Function with Parameters
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # Output: Hello, Alice!
greet("Bob") # Output: Hello, Bob!
Function with Return Value
def add(a, b):
return a + b
result = add(3, 5)
print(result) # Output: 8
# Multiple return values (returns a tuple)
def get_stats(numbers):
return min(numbers), max(numbers), sum(numbers)
minimum, maximum, total = get_stats([1, 2, 3, 4, 5])
📥 Parameters and Arguments
Types of Arguments
# Positional arguments
def greet(first_name, last_name):
print(f"Hello, {first_name} {last_name}")
greet("John", "Doe") # Positional
# Keyword arguments
greet(last_name="Doe", first_name="John") # Named
# Mixed (positional must come first!)
greet("John", last_name="Doe") # OK
# greet(first_name="John", "Doe") # ERROR!
Default Parameters
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi") # Hi, Bob!
greet("Charlie", greeting="Hey") # Hey, Charlie!
# ⚠️ Default parameters must come after non-default ones
def example(a, b=10, c=20): # OK
pass
# def example(a=10, b): # ERROR!
Mutable Default Arguments (Gotcha!)
# ❌ WRONG - mutable default is shared!
def add_item_bad(item, items=[]):
items.append(item)
return items
print(add_item_bad("a")) # ['a']
print(add_item_bad("b")) # ['a', 'b'] - Unexpected!
# ✅ CORRECT - use None as default
def add_item_good(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_good("a")) # ['a']
print(add_item_good("b")) # ['b'] - Correct!
🌟 *args and **kwargs
*args (Variable Positional Arguments)
def sum_all(*args):
print(f"args = {args}") # It's a tuple!
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# Combining with regular parameters
def greet(greeting, *names):
for name in names:
print(f"{greeting}, {name}!")
greet("Hello", "Alice", "Bob", "Charlie")
**kwargs (Variable Keyword Arguments)
def print_info(**kwargs):
print(f"kwargs = {kwargs}") # It's a dict!
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="NYC")
# Combining with regular parameters
def create_profile(name, **details):
profile = {"name": name}
profile.update(details)
return profile
user = create_profile("Bob", age=30, job="Engineer")
print(user) # {'name': 'Bob', 'age': 30, 'job': 'Engineer'}
Complete Parameter Order
# Order: regular -> *args -> keyword-only -> **kwargs
def example(a, b, *args, option=True, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"option={option}")
print(f"kwargs={kwargs}")
example(1, 2, 3, 4, 5, option=False, x=10, y=20)
Unpacking Arguments
# Unpack list/tuple with *
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
print(add(*numbers)) # Same as add(1, 2, 3)
# Unpack dict with **
def greet(name, greeting):
print(f"{greeting}, {name}!")
params = {"name": "Alice", "greeting": "Hello"}
greet(**params) # Same as greet(name="Alice", greeting="Hello")
⚡ Lambda Functions
Lambda functions are anonymous, one-line functions.
Basic Syntax
# Regular function
def add(a, b):
return a + b
# Lambda equivalent
add = lambda a, b: a + b
print(add(3, 5)) # 8
Common Use Cases
# With sorted()
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
sorted_by_score = sorted(students, key=lambda x: x[1])
print(sorted_by_score) # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
# With map()
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))
print(squares) # [1, 4, 9, 16, 25]
# With filter()
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# With reduce()
from functools import reduce
product = reduce(lambda x, y: x * y, numbers)
print(product) # 120 (1*2*3*4*5)
When to Use Lambda
# ✅ Good - simple, one-time use
sorted(data, key=lambda x: x["name"])
# ❌ Avoid - complex logic, use regular function
# lambda x: (x.split()[0].upper() if x else None)
# ❌ Avoid - reused multiple times, give it a name!
# my_func = lambda x: complex_operation(x)
🔄 Recursion
A recursive function calls itself to solve smaller instances of the same problem.
Basic Example: Factorial
def factorial(n):
# Base case
if n <= 1:
return 1
# Recursive case
return n * factorial(n - 1)
print(factorial(5)) # 120
# 5! = 5 * 4! = 5 * 4 * 3! = 5 * 4 * 3 * 2! = 5 * 4 * 3 * 2 * 1 = 120
Fibonacci
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 0, 1, 1, 2, 3, 5, 8, 13, 21...
for i in range(10):
print(fibonacci(i), end=" ")
Recursion Tips
# 1. Always have a base case (stopping condition)
# 2. Ensure progress toward base case
# 3. Python has a recursion limit (default ~1000)
import sys
print(sys.getrecursionlimit()) # 1000
# sys.setrecursionlimit(2000) # Can increase if needed
# 4. Consider iterative solutions for deep recursion
# 5. Use memoization for overlapping subproblems
🎀 Decorators (Introduction)
Decorators modify or enhance functions without changing their code.
Basic Decorator
def my_decorator(func):
def wrapper():
print("Before the function")
func()
print("After the function")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# Output:
# Before the function
# Hello!
# After the function
Decorator with Arguments
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before the function")
result = func(*args, **kwargs)
print("After the function")
return result
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Practical Decorators
import time
# Timer decorator
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function() # slow_function took 1.0012 seconds
# Debug decorator
def debug(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@debug
def add(a, b):
return a + b
add(3, 5)
📋 Docstrings
Document your functions using docstrings.
def calculate_area(length, width):
"""
Calculate the area of a rectangle.
Args:
length (float): The length of the rectangle.
width (float): The width of the rectangle.
Returns:
float: The area of the rectangle.
Raises:
ValueError: If length or width is negative.
Examples:
>>> calculate_area(5, 3)
15
>>> calculate_area(10, 10)
100
"""
if length < 0 or width < 0:
raise ValueError("Dimensions must be positive")
return length * width
# Access docstring
print(calculate_area.__doc__)
help(calculate_area)
🔄 Higher-Order Functions
Functions that take other functions as arguments or return functions.
Built-in Higher-Order Functions
numbers = [1, 2, 3, 4, 5]
# map() - apply function to each element
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# filter() - keep elements that match condition
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# sorted() with key function
words = ["banana", "apple", "cherry"]
by_length = sorted(words, key=len)
print(by_length) # ['apple', 'banana', 'cherry']
# reduce() - accumulate values
from functools import reduce
total = reduce(lambda x, y: x + y, numbers)
print(total) # 15
Creating Higher-Order Functions
def apply_operation(numbers, operation):
return [operation(x) for x in numbers]
numbers = [1, 2, 3, 4, 5]
squared = apply_operation(numbers, lambda x: x**2)
doubled = apply_operation(numbers, lambda x: x*2)
📝 Summary
| Concept | Syntax | Description |
|---|---|---|
| Define | def name(): | Create a function |
| Return | return value | Return a value |
| Default | def f(x=10): | Default parameter value |
| *args | def f(*args): | Variable positional args |
| **kwargs | def f(**kwargs): | Variable keyword args |
| Lambda | lambda x: x*2 | Anonymous function |
| Decorator | @decorator | Modify function behavior |
🎯 Next Steps
After mastering functions, proceed to 06_modules_packages to learn about organizing code into modules!