Functions in Python¶
Functions are reusable blocks of code that perform specific tasks.
Function Definition¶
def greet():
print("Hello, World!")
# Call the function
greet()
Functions with Parameters¶
def greet_person(name):
print(f"Hello, {name}!")
greet_person("Alice")
greet_person("Bob")
Functions with Return Values¶
def add_numbers(a, b):
return a + b
result = add_numbers(5, 3)
print(result) # 8
Default Parameters¶
def greet_person(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet_person("Alice") # Hello, Alice!
greet_person("Bob", "Hi") # Hi, Bob!
Keyword Arguments¶
def create_person(name, age, city):
return {"name": name, "age": age, "city": city}
# Using keyword arguments
person1 = create_person(name="Alice", age=25, city="New York")
person2 = create_person(city="London", name="Bob", age=30)
print(person1)
print(person2)
Variable Number of Arguments¶
Using *args (for positional arguments)
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
Using **kwargs (for keyword arguments)
def print_info(**info):
for key, value in info.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="New York")
Recursion¶
Recursion is when a function calls itself.
Factorial Example
def factorial(n):
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
print(factorial(5)) # 120
Fibonacci Example
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
# Print first 10 Fibonacci numbers
for i in range(10):
print(fibonacci(i), end=" ") # 0 1 1 2 3 5 8 13 21 34
Function Integration with Strings¶
def count_vowels(text):
vowels = "aeiouAEIOU"
count = 0
for char in text:
if char in vowels:
count += 1
return count
text = "Hello, World!"
print(f"Number of vowels: {count_vowels(text)}") # 3
Function Integration with Collections¶
def find_max_in_list(numbers):
if not numbers:
return None
max_num = numbers[0]
for num in numbers:
if num > max_num:
max_num = num
return max_num
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(f"Maximum: {find_max_in_list(numbers)}") # 9
def word_frequency(text):
words = text.lower().split()
frequency = {}
for word in words:
if word in frequency:
frequency[word] += 1
else:
frequency[word] = 1
return frequency
text = "the quick brown fox jumps over the lazy dog"
print(word_frequency(text))
Variable Scope¶
Local vs Global Scope
global_var = "I'm global"
def test_scope():
local_var = "I'm local"
print(global_var) # Can access global
print(local_var) # Can access local
test_scope()
# print(local_var) # Error: local_var not defined
Modifying Global Variables
counter = 0
def increment_counter():
global counter
counter += 1
increment_counter()
increment_counter()
print(counter) # 2
Nonlocal Variables (Nested Functions)
def outer():
x = "outer"
def inner():
nonlocal x
x = "inner"
print(f"Inner: {x}")
inner()
print(f"Outer: {x}")
outer()
Lambda Functions¶
Lambda functions are anonymous, single-expression functions.
# Simple 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]) # Sort by grade
print(students)
# Lambda with conditional
is_even = lambda x: "Even" if x % 2 == 0 else "Odd"
print(is_even(4)) # Even
print(is_even(5)) # Odd
Higher-Order Functions¶
Functions that take other functions as arguments or return functions.
def apply_operation(func, x, y):
return func(x, y)
def add(x, y):
return x + y
def multiply(x, y):
return x * y
print(apply_operation(add, 3, 4)) # 7
print(apply_operation(multiply, 3, 4)) # 12
Function as Return Value
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
Function Decorators¶
Decorators modify the behavior of functions.
def timer_decorator(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_decorator
def slow_function():
import time
time.sleep(1)
return "Done"
print(slow_function())
Built-in Functions¶
map() - Apply function to each element
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))
print(squares) # [1, 4, 9, 16, 25]
filter() - Filter elements based on condition
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6]
reduce() - Reduce sequence to single value
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product) # 120
Function Best Practices¶
Docstrings
def calculate_area(radius):
"""
Calculate the area of a circle given its radius.
Args:
radius (float): The radius of the circle
Returns:
float: The area of the circle
Raises:
ValueError: If radius is negative
"""
if radius < 0:
raise ValueError("Radius cannot be negative")
return 3.14159 * radius ** 2
Type Hints (Python 3.5+)
from typing import List, Dict
def process_students(students: List[Dict[str, str]]) -> Dict[str, int]:
grades = {}
for student in students:
grades[student["name"]] = int(student["grade"])
return grades
Error Handling in Functions
def divide_numbers(a: float, b: float) -> float:
"""
Divide two numbers with error handling.
"""
try:
return a / b
except ZeroDivisionError:
print("Error: Division by zero!")
return None
except TypeError:
print("Error: Invalid input types!")
return None
print(divide_numbers(10, 2)) # 5.0
print(divide_numbers(10, 0)) # Error message, None
Recursion Limits and Optimization¶
import sys
# Check recursion limit
print(f"Recursion limit: {sys.getrecursionlimit()}")
# Set new limit (careful!)
# sys.setrecursionlimit(2000)
Tail Recursion Optimization (Conceptual)
# Not tail recursive
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1) # Multiplication after recursion
# Tail recursive (conceptual - Python doesn't optimize this)
def factorial_tail(n, accumulator=1):
if n <= 1:
return accumulator
return factorial_tail(n - 1, n * accumulator) # No operation after recursion
print(factorial(5))
print(factorial_tail(5))