Day 5: Functions
What You'll Learn Today
- Defining and calling functions
- Arguments (positional, keyword, default)
- Return values
- Scope (variable visibility)
- Lambda functions
- Decorator basics
What Are Functions?
Functions are reusable blocks of code that perform specific tasks.
flowchart LR
subgraph Function["Function"]
I["Input (arguments)"] --> P["Process"]
P --> O["Output (return value)"]
end
style Function fill:#3b82f6,color:#fff
style I fill:#22c55e,color:#fff
style P fill:#8b5cf6,color:#fff
style O fill:#f59e0b,color:#fff
Why Use Functions?
| Benefit | Description |
|---|---|
| Reusability | Write once, use many times |
| Readability | Organized code is easier to understand |
| Maintainability | Changes only need to be made in one place |
| Testability | Can be tested individually |
Defining and Calling Functions
Basic Syntax
def function_name(parameters):
"""Documentation string"""
# Process
return value
Simple Example
# Define a function
def greet():
print("Hello, World!")
# Call the function
greet() # Hello, World!
Function with Parameters
def greet(name):
print(f"Hello, {name}!")
greet("Taro") # Hello, Taro!
greet("Hanako") # Hello, Hanako!
Function with Return Value
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
# Functions without return statement return None
def no_return():
print("Processing")
result = no_return() # Processing
print(result) # None
Types of Arguments
Positional Arguments
Arguments are passed in order:
def describe_person(name, age, city):
print(f"{name} is {age} years old and lives in {city}.")
describe_person("Taro", 25, "Tokyo")
# Taro is 25 years old and lives in Tokyo.
Keyword Arguments
You can specify argument names:
describe_person(name="Hanako", city="Osaka", age=30)
# Hanako is 30 years old and lives in Osaka.
# Mix positional and keyword (positional first)
describe_person("Jiro", age=20, city="Nagoya")
Default Arguments
You can set default values:
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Taro") # Hello, Taro!
greet("Taro", "Hi") # Hi, Taro!
greet("Taro", greeting="Good morning") # Good morning, Taro!
Note: Default arguments must come after non-default arguments.
# Correct
def func(a, b, c=10):
pass
# Error
# def func(a, b=10, c): # SyntaxError
# pass
Variable-Length Arguments (*args)
Accept any number of positional arguments:
def sum_all(*numbers):
total = 0
for n in numbers:
total += n
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
Variable-Length Keyword Arguments (**kwargs)
Accept any number of keyword arguments:
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Taro", age=25, city="Tokyo")
# name: Taro
# age: 25
# city: Tokyo
Combining Argument Types
def example(a, b, *args, **kwargs):
print(f"a: {a}")
print(f"b: {b}")
print(f"args: {args}")
print(f"kwargs: {kwargs}")
example(1, 2, 3, 4, 5, x=10, y=20)
# a: 1
# b: 2
# args: (3, 4, 5)
# kwargs: {'x': 10, 'y': 20}
Return Values
Returning a Single Value
def square(n):
return n ** 2
result = square(5)
print(result) # 25
Returning Multiple Values
Values are returned as a tuple:
def min_max(numbers):
return min(numbers), max(numbers)
minimum, maximum = min_max([3, 1, 4, 1, 5, 9])
print(minimum) # 1
print(maximum) # 9
Early Return
Exit early based on conditions:
def divide(a, b):
if b == 0:
return None # Prevent division by zero
return a / b
print(divide(10, 2)) # 5.0
print(divide(10, 0)) # None
Scope
The range where a variable is valid is called its "scope."
flowchart TB
subgraph Global["Global Scope"]
G["global_var = 'global'"]
subgraph Local["Local Scope (inside function)"]
L["local_var = 'local'"]
end
end
style Global fill:#3b82f6,color:#fff
style Local fill:#8b5cf6,color:#fff
Local and Global Variables
global_var = "global"
def example():
local_var = "local"
print(global_var) # Can access global
print(local_var) # Local variable
example()
# print(local_var) # Error! Can't access outside function
The global Keyword
To modify global variables inside a function:
counter = 0
def increment():
global counter
counter += 1
increment()
increment()
print(counter) # 2
Note: Avoid overusing global. Use return values instead.
# Recommended approach
def increment(counter):
return counter + 1
counter = 0
counter = increment(counter)
counter = increment(counter)
print(counter) # 2
Documentation Strings (Docstrings)
Describe what your function does:
def calculate_area(width, height):
"""
Calculate the area of a rectangle.
Args:
width: The width of the rectangle
height: The height of the rectangle
Returns:
The area (width * height)
"""
return width * height
# View documentation
print(calculate_area.__doc__)
help(calculate_area)
Lambda Functions
Define short anonymous functions:
# Regular function
def square(x):
return x ** 2
# Lambda function
square = lambda x: x ** 2
print(square(5)) # 25
Lambda Use Cases
# As a sort key
students = [
{"name": "Taro", "score": 80},
{"name": "Hanako", "score": 95},
{"name": "Jiro", "score": 70}
]
# Sort by score
students.sort(key=lambda x: x["score"])
print(students)
# With filter()
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even = list(filter(lambda x: x % 2 == 0, numbers))
print(even) # [2, 4, 6, 8, 10]
# With map()
squares = list(map(lambda x: x ** 2, numbers))
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Functions as Arguments
In Python, functions can be passed as arguments:
def apply_operation(x, y, operation):
return operation(x, y)
def add(a, b):
return a + b
def multiply(a, b):
return a * b
print(apply_operation(5, 3, add)) # 8
print(apply_operation(5, 3, multiply)) # 15
Decorators
A mechanism to extend function behavior:
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} completed")
return result
return wrapper
@log_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Taro")
# Calling function greet
# Hello, Taro!
# Function greet completed
Practical Decorator: Timing Execution
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}: {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "Done"
slow_function() # slow_function: 1.00XX seconds
Recursive Functions
Functions that call themselves:
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120 (5 * 4 * 3 * 2 * 1)
flowchart TB
A["factorial(5)"] --> B["5 * factorial(4)"]
B --> C["4 * factorial(3)"]
C --> D["3 * factorial(2)"]
D --> E["2 * factorial(1)"]
E --> F["1"]
style A fill:#3b82f6,color:#fff
style F fill:#22c55e,color:#fff
Fibonacci Sequence
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
for i in range(10):
print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13 21 34
Note: Python has a default recursion limit of about 1000. Avoid deep recursion or convert to loops.
Summary
| Concept | Description | Example |
|---|---|---|
| def | Define a function | def func(): |
| return | Return a value | return result |
| Positional args | Pass by position | func(1, 2, 3) |
| Keyword args | Pass by name | func(a=1, b=2) |
| Default args | Default values | def func(x=10): |
| *args | Variable positional | def func(*args): |
| **kwargs | Variable keyword | def func(**kwargs): |
| lambda | Anonymous function | lambda x: x * 2 |
Key Takeaways
- Functions improve code reusability and readability
- Default argument values are evaluated once at definition time
- Local variables are only valid inside the function
- Minimize use of
global - Use lambda for short operations, regular functions for complex logic
Practice Exercises
Exercise 1: Basics
Create a function max_of_two(a, b) that returns the larger of two numbers.
Exercise 2: List Processing
Create a function filter_even(numbers) that takes a list and returns a new list containing only even numbers.
Challenge
Create a function average(*args) that accepts any number of arguments and returns their average. Return None if no arguments are provided.
References
Next Up: In Day 6, you'll learn about "Object-Oriented Programming." Enter the world of classes and objects!