Writing Logic and Functions
Functions: Writing Reusable Code
7 min read
The copy-paste trap
Imagine you need to calculate the area of a rectangle in five different places in your program. You could write width * height five times. But what happens when requirements change and you need to add validation? You'd have to find and update all five places — and probably miss one.
Functions solve this. A function packages a piece of logic under a name. You write it once, call it anywhere. If you need to change the logic, you change it in one place.
Defining a function
Use the def keyword to define a function:
def greet_user(name):
return f"Hello, {name}! Welcome to Python."
Breaking this down:
def— tells Python you're defining a functiongreet_user— the name you're giving the function(name)— a parameter (an input the function expects)- The indented block — the code that runs when the function is called
return— sends a value back to whoever called the function
Calling a function
Define it once, then call it by name:
def greet_user(name):
return f"Hello, {name}! Welcome to Python."
message = greet_user("Sonia")
print(message)
Output:
Hello, Sonia! Welcome to Python.
When Python sees greet_user("Sonia"):
- It jumps into the function definition
- Sets
name = "Sonia" - Runs the code inside
- Returns the result back to the line that called it
- Continues from there
Functions with multiple parameters
Functions can accept multiple inputs:
def calculate_area(width, height):
return width * height
area = calculate_area(5, 10)
print("Area:", area)
# Area: 50
When calling with multiple arguments, they match the parameters in order: width = 5, height = 10.
Default parameter values
You can give parameters a default value — used when no argument is provided for that parameter:
def make_greeting(name, language="English"):
if language == "Spanish":
return f"Hola, {name}!"
elif language == "French":
return f"Bonjour, {name}!"
return f"Hello, {name}!"
print(make_greeting("Mina")) # Hello, Mina!
print(make_greeting("Mina", language="Spanish")) # Hola, Mina!
print(make_greeting("Mina", language="French")) # Bonjour, Mina!
If you call make_greeting("Mina") without specifying a language, it defaults to "English".
A default value can only follow other defaults — once a parameter has one, every parameter after it must have one too:
def make_greeting(language="English", name): # SyntaxError
...
Python can't tell whether a value provided in a call belongs to language or name, so it refuses to define the function at all.
Keyword arguments
So far, arguments have been matched to parameters by position — the first value fills the first parameter, and so on. Keyword arguments let you match them by name instead, in any order:
def make_greeting(name, language="English"):
if language == "Spanish":
return f"Hola, {name}!"
return f"Hello, {name}!"
print(make_greeting(language="Spanish", name="Mina")) # Hola, Mina!
print(make_greeting(name="Mina", language="Spanish")) # Hola, Mina!
Both calls above work identically — with keyword arguments, the order you write them in doesn't matter. This is especially handy when a function has several parameters with defaults and you only want to override one:
def show_range(upper_limit=10, step=1):
print(list(range(0, upper_limit, step)))
show_range(step=2) # only override step, keep upper_limit's default
One rule to keep in mind: once you start passing arguments by keyword in a call, every argument after it must also be passed by keyword.
make_greeting(name="Mina", "Spanish") # SyntaxError
The return statement
return sends a value back from the function to the caller. Once Python hits return, it exits the function immediately.
def is_even(number):
if number % 2 == 0:
return True
return False
print(is_even(4)) # True
print(is_even(7)) # False
A function without a return statement (or with a bare return) gives back None:
def say_hello(name):
print(f"Hello, {name}!") # prints, but doesn't return
result = say_hello("Alex")
print(result) # None
Functions with no parameters
Not every function needs inputs:
def show_menu():
print("1. Start new game")
print("2. Load saved game")
print("3. Settings")
print("4. Quit")
show_menu()
Functions like this are useful for organizing code into named sections, even when there's nothing to pass in.
Why functions matter
1. Write once, use everywhere
def calculate_tax(price, rate=0.08):
return price * rate
# Use it anywhere without rewriting the logic
item1_tax = calculate_tax(29.99)
item2_tax = calculate_tax(49.99, rate=0.1) # different tax rate
print(f"Tax on item 1: ${item1_tax:.2f}")
print(f"Tax on item 2: ${item2_tax:.2f}")
2. One place to fix bugs
If the tax calculation ever needs to change, you update one function — not every place you've computed a tax.
3. Readable code
Functions make code self-documenting. A function named validate_email() tells you exactly what it does without reading the implementation.
A practical example: student grade tracker
Here's a small but complete program that uses functions to organize logic:
def calculate_average(scores):
return sum(scores) / len(scores)
def letter_grade(average):
if average >= 90:
return "A"
elif average >= 80:
return "B"
elif average >= 70:
return "C"
elif average >= 60:
return "D"
return "F"
def print_report(name, scores):
avg = calculate_average(scores)
grade = letter_grade(avg)
print(f"Student: {name}")
print(f"Average: {avg:.1f}")
print(f"Grade: {grade}")
print_report("Sonia", [85, 92, 78, 88, 91])
Output:
Student: Sonia
Average: 86.8
Grade: B
Each function does one thing. print_report calls calculate_average and letter_grade — functions calling other functions is completely normal and encouraged.
Returning multiple values
return isn't limited to a single value. Separate values with commas, and Python automatically packs them into a tuple:
def points(x):
y = 1 - x
return x, y
result = points(0.3)
print(result) # (0.3, 0.7)
At the call site, you can unpack the returned tuple straight into separate variables — the same tuple-unpacking syntax you'd use for any tuple:
x_val, y_val = points(0.3)
print(x_val, y_val) # 0.3 0.7
This is the standard way to hand back more than one result from a function — for example, a function that computes both an average and a total, or both a minimum and a maximum.
Scope: where variables live
A variable created inside a function only exists while that function is running. It's not accessible outside:
def my_function():
x = 10 # only exists inside this function
print(x)
my_function() # prints 10
print(x) # NameError: name 'x' is not defined
This is called local scope. Variables defined outside all functions are in global scope and can be read anywhere:
counter = 10
def print_counter():
print(counter) # reads the global counter just fine
print_counter() # 10
Assigning to a global variable's name creates a local one instead
Reading a global variable works without any special syntax. But assigning to a name inside a function always creates a new local variable, even if a global variable already has that name:
counter = 10
def change_counter():
counter = 20 # creates a local `counter`, doesn't touch the global one
print("Counter in:", counter)
change_counter() # Counter in: 20
print("Counter out:", counter) # Counter out: 10 (unchanged)
Using global to modify the outer variable
To actually change a global variable from inside a function, declare it with the global keyword first:
counter = 10
def change_counter():
global counter
counter = 20
print("Counter in:", counter)
change_counter() # Counter in: 20
print("Counter out:", counter) # Counter out: 20 (changed)
Use global sparingly — functions that quietly modify variables outside their own scope are harder to reason about. Prefer returning a new value and reassigning it at the call site instead.
Key takeaway
Functions package reusable logic under a name. You define them with def, give them parameters for inputs, and use return to send results back. Default parameter values and keyword arguments make functions flexible to call; global lets you opt into modifying a variable outside a function, though it's best used sparingly. The payoff: write the logic once, use it everywhere, fix it in one place.
What's next?
You've covered the core of Python — the building blocks every program is made from. The final lesson brings in two more essential tools: modules (borrowing code others have written) and file I/O (saving and loading data to and from files).