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