Functions
Functions are one of the core building blocks of any language. Here are some examples of how to use functions in Python
Basic Function Definition
The simplest function looks like this:
def greet():
print("Hello, World")
greet() # Call the function
Functions are defined with the def
keyword, followed by the function name and parentheses. The colon starts the function body, the content of the function must be indented.
Functions with Parameters
To pass a parameter as input to a function:
def greet_person(name):
print(f"Hello, {name}")
greet_person("Alice")
# Output: Hello, Alice
A function with multiple parameters:
def intro(name, city):
print(f"Hi, I'm {name} from {city}")
intro("Sarah", "New York")
# Output: Hi, I'm Sarah from New York
Default parameters are specified with the parameter name:
def announce_score(team="Home", score=0):
print(f"{team} team has scored {score} points")
# Usage examples
announce_score("Warriors", 102)
# Output: Warriors team has scored 102 points
announce_score("Lakers")
# Output: Lakers team has scored 0 points
announce_score()
# Output: Home team has scored 0 points
A parameter with a default value is an optional parameter, a parameter without a default value is a required parameter. You can mix required and optional parameters, but optional parameters must come last:
def create_user(username, email, role="user", active=True):
return {
"username": username,
"email": email,
"role": role,
"active": active
}
user1 = create_user("alice", "alice@example.com")
If you call a function without providing all required arguments, Python will raise a TypeError
:
def intro(name, city):
print(f"Hi, I'm {name} from {city}")
intro("Sarah")
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: intro() missing 1 required positional argument: 'city'
👉 Your Turn: Before we move on, try writing a function called
describe_pet()
that takes a pet’s name and animal type, with animal type defaulting to “dog”. Make it return a string like “Buddy is a friendly dog.”
Return Values
Use the return
statement to return values from functions:
def add(a, b):
return a + b
result = add(5, 3)
print(result)
# Output: 8
Without an explicit return, functions return None
:
def say_hello():
print("Hello")
result = say_hello()
# Output: Hello
print(result)
# Output: None
Multiple Return Values
Python makes it easy to return multiple values using tuples:
def get_name_parts(full_name):
parts = full_name.split()
first_name = parts[0]
last_name = parts[-1]
return first_name, last_name
first, last = get_name_parts("John Doe")
print(f"First: {first}, Last: {last}")
# Output: First: John, Last: Doe
Keyword Arguments
You can call functions using parameter names, this makes code easier to read since you don’t have to remember what order the arguments are in:
def create_rectangle(width, height, color="blue"):
return f"A {color} rectangle: {width}x{height}"
# These are all equivalent:
rect1 = create_rectangle(10, 5)
rect2 = create_rectangle(width=10, height=5)
rect3 = create_rectangle(height=5, width=10) # Order doesn't matter with keywords
rect4 = create_rectangle(10, height=5, color="red") # Mix positional and keyword
Variable Arguments (*args)
Sometimes you don’t know how many arguments you’ll get, use *
before the parameter name to accept any number of positional arguments, this creates a tuple of arguments assigned to the parameter name.
def sum_all(*numbers):
total = 0
for num in numbers:
total += num
return total
print(sum_all(1, 2, 3)) # Output: 6
print(sum_all(1, 2, 3, 4, 5)) # Output: 15
print(sum_all()) # Output: 0
Keyword Variable Arguments (**kwargs)
For flexible keyword arguments use two **
with the parameter name, this creates a dictionary of keyword arguments assigned to the parameter name.
def create_profile(**info):
profile = {}
for key, value in info.items():
profile[key] = value
return profile
user = create_profile(name="Alice", age=30, city="Boston", job="Engineer")
print(user)
# Output: {'name': 'Alice', 'age': 30, 'city': 'Boston', 'job': 'Engineer'}
You can combine all argument types, but I wouldn’t recommend it. To make code easiest to understand and most flexible, use keyword arguments.
def flexible_function(required_arg, default_arg="default", *args, **kwargs):
print(f"Required: {required_arg}")
print(f"Default: {default_arg}")
print(f"Extra args: {args}")
print(f"Keyword args: {kwargs}")
flexible_function("hello", "world", 1, 2, 3, name="Alice", age=25)
# Output:
# Required: hello
# Default: world
# Extra args: (1, 2, 3)
# Keyword args: {'name': 'Alice', 'age': 25}
Lambda Functions
For simple, one-line functions, you can use a lambda function:
# Regular function
def square(x):
return x * x
# Lambda equivalent
square_lambda = lambda x: x * x
print(square(5)) # Output: 25
print(square_lambda(5)) # Output: 25
Lambdas are most useful with functions like map()
, filter()
, and sorted()
:
numbers = [1, 2, 3, 4, 5]
# Square all numbers
squared = list(map(lambda x: x * x, numbers))
print(squared)
# Output: [1, 4, 9, 16, 25]
# Filter even numbers
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)
# Output: [2, 4]
# Sort by custom criteria
people = [("Alice", 25), ("Bob", 30), ("Charlie", 20)]
sorted_by_age = sorted(people, key=lambda person: person[1])
print(sorted_by_age)
# Output: [('Charlie', 20), ('Alice', 25), ('Bob', 30)]
Function Documentation
Document functions using docstrings:
def calculate_compound_interest(principal, rate, time, compound_frequency=1):
"""
Calculate compound interest.
Args:
principal (float): Initial amount of money
rate (float): Annual interest rate (as decimal, e.g., 0.05 for 5%)
time (float): Time in years
compound_frequency (int): How many times interest compounds per year
Returns:
tuple: (final_amount, interest_earned)
Example:
>>> amount, interest = calculate_compound_interest(1000, 0.05, 2)
>>> print(f"Amount: ${amount:.2f}, Interest: ${interest:.2f}")
"""
final_amount = principal * (1 + rate/compound_frequency) ** (compound_frequency * time)
interest_earned = final_amount - principal
return final_amount, interest_earned
# Usage
amount, interest = calculate_compound_interest(1000, 0.05, 2, 4)
print(f"Final amount: ${amount:.2f}")
print(f"Interest earned: ${interest:.2f}")
Nested Functions
You can define functions inside other functions. A primary reason for this is to create closures. A closure allows the inner function to remember and access variables from the outer function’s scope, even after the outer function has finished executing. This pattern is useful for creating function factories or specialized functions tailored by parameters passed to the outer function.
Here’s a practical example of a closure - creating a counter function:
def create_counter(start=0):
count = start
def counter():
nonlocal count
count += 1
return count
return counter
# Create different counters
page_counter = create_counter(1)
error_counter = create_counter(0)
print(page_counter()) # Output: 2
print(page_counter()) # Output: 3
print(error_counter()) # Output: 1
print(page_counter()) # Output: 4
The inner counter
function “closes over” the count
variable from the outer function’s scope. Each time we call create_counter()
, it creates a new closure with its own count
variable that persists between calls to the returned function.
Tips on Using Functions
Here are the patterns I aim to follow when writing functions:
- Keep functions small and focused - each function should do one thing well
- Use descriptive names -
calculate_tax()
is better thancalc()
- Return early when possible - avoid deep nesting
- Use type hints for clarity, see Type Hints chapter