Python Functions
Functions are reusable blocks of code that perform specific tasks. They help organize code, reduce duplication, and improve maintainability.
What are Functions?
Functions are named blocks of code that perform a specific task. They take input (parameters), process it, and optionally return output. Functions promote code reusability and modularity.
"Functions break down complex problems into smaller, manageable pieces."
Defining Functions
Use the def keyword to define functions:
# Basic function definition
def greet():
print("Hello, World!")
# Calling the function
greet() # Output: Hello, World!
# Function with parameters
def greet_person(name):
print(f"Hello, {name}!")
greet_person("Alice") # Output: Hello, Alice!
# Function with return value
def add_numbers(a, b):
return a + b
result = add_numbers(5, 3)
print(result) # Output: 8
# Function with multiple return values
def get_coordinates():
return 10, 20
x, y = get_coordinates()
print(f"x: {x}, y: {y}") # Output: x: 10, y: 20
Function Parameters
Functions can accept different types of parameters:
# Default parameters
def greet(name="World"):
print(f"Hello, {name}!")
greet() # Hello, World!
greet("Alice") # Hello, Alice!
# Keyword arguments
def describe_person(name, age, city):
print(f"{name} is {age} years old and lives in {city}")
describe_person(age=25, name="Bob", city="NYC")
# Output: Bob is 25 years old and lives in NYC
# Variable-length arguments (*args)
def sum_all(*numbers):
total = 0
for num in numbers:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# Variable-length keyword arguments (**kwargs)
def print_info(**info):
for key, value in info.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="Boston")
# Output:
# name: Alice
# age: 25
# city: Boston
Scope and Lifetime
Variables have different scopes in Python:
# Local scope
def my_function():
local_var = "I'm local"
print(local_var)
my_function() # I'm local
# print(local_var) # NameError: name 'local_var' is not defined
# Global scope
global_var = "I'm global"
def access_global():
print(global_var) # Can access global variable
access_global() # I'm global
# Modifying global variables
count = 0
def increment():
global count
count += 1
increment()
print(count) # 1
# nonlocal (for nested functions)
def outer():
x = "outer"
def inner():
nonlocal x
x = "inner"
print(f"inner: {x}")
inner()
print(f"outer: {x}")
outer()
# Output:
# inner: inner
# outer: inner
Lambda Functions
Lambda functions are anonymous, single-expression functions:
# Basic lambda
square = lambda x: x ** 2
print(square(5)) # 25
# Lambda with multiple parameters
add = lambda x, y: x + y
print(add(3, 4)) # 7
# Lambda in sorting
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
students.sort(key=lambda student: student[1])
print(students) # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
# Lambda with conditional
max_value = lambda a, b: a if a > b else b
print(max_value(10, 20)) # 20
# Using lambda with map, filter, reduce
numbers = [1, 2, 3, 4, 5]
# map
squares = list(map(lambda x: x**2, numbers))
print(squares) # [1, 4, 9, 16, 25]
# filter
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# reduce
from functools import reduce
product = reduce(lambda x, y: x * y, numbers)
print(product) # 120
Function Documentation
Document functions with 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
Example:
>>> calculate_area(5, 3)
15
"""
return length * width
print(calculate_area.__doc__)
help(calculate_area)
Function Annotations
Type hints help document expected types:
def greet_person(name: str, age: int) -> str:
return f"Hello, {name}! You are {age} years old."
def add_numbers(a: float, b: float) -> float:
return a + b
# Annotations don't enforce types at runtime
result = add_numbers("hello", "world") # This works but may not be intended
# Using typing module for complex types
from typing import List, Dict, Optional
def process_data(data: List[Dict[str, int]], threshold: Optional[int] = None) -> List[int]:
if threshold is None:
threshold = 0
return [item['value'] for item in data if item['value'] > threshold]
Recursion
Functions can call themselves:
# Factorial with recursion
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
# Fibonacci with recursion
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(6)) # 8
# Recursion with memoization
memo = {}
def fibonacci_memo(n):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci_memo(n - 1) + fibonacci_memo(n - 2)
return memo[n]
print(fibonacci_memo(10)) # 55
Best Practices
- Use descriptive names: Function names should clearly indicate their purpose
- Keep functions small: Each function should do one thing well
- Use docstrings: Document what the function does, its parameters, and return values
- Use type hints: Add type annotations for better code clarity
- Avoid global variables: Pass data as parameters instead
- Handle edge cases: Consider what happens with invalid inputs
- Use meaningful parameter names: Parameters should be self-documenting
- Return early: Use guard clauses to handle error conditions
Common Function Patterns
# Factory function
def create_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
double = create_multiplier(2)
triple = create_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Callback function
def process_list(items, callback):
result = []
for item in items:
result.append(callback(item))
return result
def square(x):
return x ** 2
numbers = [1, 2, 3, 4, 5]
squared = process_list(numbers, square)
print(squared) # [1, 4, 9, 16, 25]
# Decorator pattern
def timer(func):
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.2f} seconds")
return result
return wrapper
@timer
def slow_function():
import time
time.sleep(1)
return "Done"
print(slow_function())
Functions are the building blocks of Python programs. They allow you to organize code into reusable, testable units that make your programs more maintainable and easier to understand.