Functions
Introduction
Functions are reusable blocks of code that perform a specific task. Instead of writing the same code multiple times, we can group it into a function and call it whenever needed.
In Python, functions are first-class objects. This means:
- They can be stored in variables.
- They can be passed as arguments to other functions.
- They can be returned from other functions.
- They can be created at runtime.
Python offers several ways to define functions, including regular functions, lambda functions, and generator functions. Understanding these helps you write more flexible and maintainable code. Functions are also crucial for scope management and organizing large programs.
Concepts
Function Definition
- Defined with the
defkeyword. - Can have default parameters.
- Support *args and **kwargs for variable arguments.
Function Types
- Regular functions: Defined with
def. - Lambda functions: Anonymous functions for simple operations.
- Generator functions: Functions that yield values using
yield. - Async functions: Functions that support asynchronous operations.
Scope
- Local scope: Variables inside functions.
- Global scope: Variables at module level.
- Nonlocal scope: Variables in enclosing functions.
Examples
# Regular function
def add(a, b):
return a + b
# Function with default parameter
def greet(name="Guest"):
return f"Welcome, {name}!"
# Lambda function
multiply = lambda a, b: a * b
# Variable arguments
def sum_all(*args):
return sum(args)
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
# Examples usage
print(add(5, 3)) # 8
print(greet()) # Welcome, Guest!
print(greet("Alice")) # Welcome, Alice!
print(multiply(4, 5)) # 20
print(sum_all(1, 2, 3, 4, 5)) # 15
print_info(name="Bob", age=25, city="London")
Challenges
1. max(a,b)
- Write a function
max_value(a, b)that returns the larger of two numbers.
💡 Show Answer
def max_value(a, b):
return a if a > b else b
print(max_value(5, 12)) # 12
# Alternative using built-in max()
def max_value_alt(a, b):
return max(a, b)
# One-liner lambda version
max_lambda = lambda a, b: a if a > b else b
print(max_lambda(8, 3)) # 8
2. is_even lambda
- Create a lambda function
is_eventhat returnsTrueif the number is even.
💡 Show Answer
is_even = lambda n: n % 2 == 0
print(is_even(4)) # True
print(is_even(7)) # False
# Using it with filter
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(is_even, numbers))
print(even_numbers) # [2, 4, 6, 8, 10]
3. reverse_string
- Write a function
reverse_string(text)that reverses a string and returns it.
💡 Show Answer
def reverse_string(text):
return text[::-1]
print(reverse_string("The Wind in the Willows"))
# swolliW eht ni dniW ehT
# Alternative methods
def reverse_string_alt1(text):
return ''.join(reversed(text))
def reverse_string_alt2(text):
result = ""
for char in text:
result = char + result
return result
# Test all methods
test_string = "Python"
print(reverse_string(test_string)) # nohtyP
print(reverse_string_alt1(test_string)) # nohtyP
print(reverse_string_alt2(test_string)) # nohtyP
4. greet_user default
- Create a function greet_user(name) that uses a default parameter "Guest" if no name is given.
💡 Show Answer
def greet_user(name="Guest"):
return f"Hello, {name}!"
print(greet_user()) # Hello, Guest!
print(greet_user("Alice")) # Hello, Alice!
# More elaborate version
def greet_user_advanced(name="Guest", greeting="Hello", punctuation="!"):
return f"{greeting}, {name}{punctuation}"
print(greet_user_advanced()) # Hello, Guest!
print(greet_user_advanced("Bob", "Hi")) # Hi, Bob!
print(greet_user_advanced("Carol", "Good morning", ".")) # Good morning, Carol.
5. sum_array
- Write a function sum_array(numbers) that takes a list and returns the sum of all its elements.
💡 Show Answer
def sum_array(numbers):
return sum(numbers)
print(sum_array([1, 2, 3, 4, 5])) # 15
# Manual implementation
def sum_array_manual(numbers):
total = 0
for num in numbers:
total += num
return total
# Using reduce (functional approach)
from functools import reduce
def sum_array_reduce(numbers):
return reduce(lambda a, b: a + b, numbers, 0)
# Test all implementations
test_list = [1, 2, 3, 4, 5]
print(sum_array(test_list)) # 15
print(sum_array_manual(test_list)) # 15
print(sum_array_reduce(test_list)) # 15
6. Function vs Lambda
- Demonstrate the difference between a regular function and a lambda function.
💡 Show Answer
# Regular function
def square(x):
return x * x
# Lambda function
square_lambda = lambda x: x * x
print(square(5)) # 25
print(square_lambda(5)) # 25
# Lambda functions are useful for short operations
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Multiple operations with lambda
operations = [
lambda x: x + 1,
lambda x: x * 2,
lambda x: x ** 2
]
for op in operations:
print(f"Operation on 5: {op(5)}")
7. Delayed execution with timer
- Use threading.Timer to print "Hello World" after 3 seconds.
💡 Show Answer
import threading
import time
def delayed_hello():
print("Hello World")
# Using Timer
timer = threading.Timer(3.0, delayed_hello)
timer.start()
print("Timer started, waiting 3 seconds...")
timer.join() # Wait for timer to complete
# Alternative using time.sleep
def delayed_execution():
print("Starting delay...")
time.sleep(3)
print("Hello World after 3 seconds!")
delayed_execution()
# Using asyncio for async delay
import asyncio
async def async_hello():
print("Async timer started...")
await asyncio.sleep(3)
print("Hello World (async)!")
# Uncomment to run (requires async context)
# asyncio.run(async_hello())
8. apply_twice
- Write a function apply_twice(func, value) that takes a function and a value, then applies the function to the value twice.
💡 Show Answer
def apply_twice(func, value):
return func(func(value))
def double_value(val):
return val * 2
result = apply_twice(double_value, 5)
print(result) # 20 (5 * 2 * 2)
# More examples
def add_one(x):
return x + 1
print(apply_twice(add_one, 10)) # 12
# With lambda
print(apply_twice(lambda x: x ** 2, 3)) # 81 (3^2)^2
# Generic apply_n_times function
def apply_n_times(func, value, n):
result = value
for _ in range(n):
result = func(result)
return result
print(apply_n_times(double_value, 2, 4)) # 32 (2*2*2*2*2)
9. Recursive factorial
- Create a recursive function factorial(n) that returns the factorial of n.
💡 Show Answer
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
print(factorial(10)) # 3628800
# With input validation
def factorial_safe(n):
if not isinstance(n, int) or n < 0:
raise ValueError("Factorial is only defined for non-negative integers")
if n == 0 or n == 1:
return 1
return n * factorial_safe(n - 1)
# Iterative version for comparison
def factorial_iterative(n):
if n < 0:
raise ValueError("Factorial is only defined for non-negative integers")
result = 1
for i in range(1, n + 1):
result *= i
return result
# Test both implementations
for i in range(6):
recursive_result = factorial(i)
iterative_result = factorial_iterative(i)
print(f"{i}! = {recursive_result} (recursive) = {iterative_result} (iterative)")
10. make_multiplier
- Write a function make_multiplier(x) that returns a new function.
- The returned function should multiply its input by x.
💡 Show Answer
def make_multiplier(x):
def multiplier(n):
return n * x
return multiplier
# Create specialized multiplier functions
double = make_multiplier(2)
triple = make_multiplier(3)
times_ten = make_multiplier(10)
print(double(5)) # 10
print(triple(4)) # 12
print(times_ten(7)) # 70
# Using lambda for shorter syntax
def make_multiplier_lambda(x):
return lambda n: n * x
quadruple = make_multiplier_lambda(4)
print(quadruple(6)) # 24
# More complex closure example
def make_accumulator(start=0):
total = start
def accumulator(value):
nonlocal total
total += value
return total
return accumulator
acc = make_accumulator(10)
print(acc(5)) # 15
print(acc(3)) # 18
print(acc(2)) # 20
# Function factory with multiple operations
def make_calculator(operation):
operations = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y,
'divide': lambda x, y: x / y if y != 0 else "Cannot divide by zero"
}
return operations.get(operation, lambda x, y: "Unknown operation")
add_func = make_calculator('add')
mult_func = make_calculator('multiply')
print(add_func(10, 5)) # 15
print(mult_func(4, 6)) # 24