Lecture 5: Functions in Python
1. Introduction to Functions
Functions are reusable blocks of code that perform a specific task.
# Basic function definition
def greet():
print("Hello, World!")
# Function call
greet() # Output: Hello, World!
# Function with parameters
def greet_person(name):
print(f"Hello, {name}!")
greet_person("Alice") # Output: Hello, Alice!
Advantages of Functions
- Code Reusability: Write once, use multiple times
- Modularity: Break down complex problems into smaller parts
- Readability: Makes code more organized and easier to understand
- Maintainability: Easier to debug and update code
- Abstraction: Hide implementation details
2. Function Parameters and Return Values
Positional and Keyword Arguments
# Positional arguments
def describe_pet(animal_type, pet_name):
print(f"I have a {animal_type} named {pet_name}.")
describe_pet('hamster', 'Harry') # Positional arguments
describe_pet(pet_name='Harry', animal_type='hamster') # Keyword arguments
Default Parameters
def describe_pet(pet_name, animal_type='dog'):
print(f"I have a {animal_type} named {pet_name}.")
describe_pet('Willie') # Uses default animal_type
describe_pet('Harry', 'hamster') # Override default
Return Values
def get_formatted_name(first_name, last_name, middle_name=''):
"""Return a full name, neatly formatted."""
if middle_name:
full_name = f"{first_name} {middle_name} {last_name}"
else:
full_name = f"{first_name} {last_name}"
return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician) # Jimi Hendrix
musician = get_formatted_name('john', 'hooker', 'lee')
print(musician) # John Lee Hooker
3. Function Documentation and Type Hints
def calculate_area(radius: float) -> float:
"""
Calculate the area of a circle.
Args:
radius (float): The radius of the circle
Returns:
float: The area of the circle
"""
import math
return math.pi * radius ** 2
# Accessing docstring
print(calculate_area.__doc__)
# Type hints (Python 3.5+)
def greet(name: str, age: int) -> str:
return f"Hello {name}, you are {age} years old."
4. Variable-Length Arguments
*args (Arbitrary Positional Arguments)
def make_pizza(*toppings):
"""Print the list of toppings that have been requested."""
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
**kwargs (Arbitrary Keyword Arguments)
def build_profile(first, last, **user_info):
"""Build a dictionary containing everything we know about a user."""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)
5. Scope and Namespaces
# Global variable
x = "global x"
def test():
# Local variable
y = "local y"
print(y) # local y
print(x) # global x (readable but not writable)
test()
# Modifying global variable
def modify_global():
global x
x = "modified global x"
modify_global()
print(x) # modified global x
6. Lambda Functions
Small anonymous functions defined with the lambda keyword.
# Basic lambda
square = lambda x: x ** 2
print(square(5)) # 25
# With multiple arguments
multiply = lambda x, y: x * y
print(multiply(3, 4)) # 12
# Sorting with lambda
points = [(1, 2), (3, 1), (5, 4), (2, 3)]
points_sorted = sorted(points, key=lambda point: point[1])
print(points_sorted) # [(3, 1), (1, 2), (2, 3), (5, 4)]
7. map(), filter(), and reduce()
map()
numbers = [1, 2, 3, 4, 5]
# Using map with a function
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Equivalent list comprehension
squared = [x**2 for x in numbers]
filter()
# Using filter with a function
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# Equivalent list comprehension
evens = [x for x in numbers if x % 2 == 0]
reduce()
from functools import reduce
# Using reduce to calculate product
product = reduce((lambda x, y: x * y), [1, 2, 3, 4])
print(product) # 24
8. Inner Functions and Closures
# Inner function
def outer():
print("Outer function")
def inner():
print("Inner function")
return inner
# Closure example
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
double = make_multiplier(2)
print(double(5)) # 10
triple = make_multiplier(3)
print(triple(5)) # 15
9. Passing Mutable vs Immutable Objects
# Immutable objects (integers, strings, tuples)
def modify_immutable(x):
x = x + 1
print("Inside function:", x)
num = 5
modify_immutable(num) # Inside function: 6
print("Outside function:", num) # 5 (unchanged)
# Mutable objects (lists, dictionaries, sets)
def modify_mutable(my_list):
my_list.append(4)
print("Inside function:", my_list)
numbers = [1, 2, 3]
modify_mutable(numbers) # Inside function: [1, 2, 3, 4]
print("Outside function:", numbers) # [1, 2, 3, 4] (changed)
10. Recursion
A function that calls itself is called a recursive function.
# Factorial using recursion
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n - 1)
print(factorial(5)) # 120
# Fibonacci sequence
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