Image by Author
# Introduction
You have written your first Python function. It works. You run it, it produces the right output, and you feel that rush of accomplishment. Then you look at it again two weeks later and think: “What does this even do?”
Writing readable Python functions is not about being smart or advanced. It is about writing functions that communicate their intent clearly, handle their responsibilities cleanly, and make the next person’s job easier.
Let’s learn how to write functions that read like good prose, not cryptic puzzles that readers find difficult to wrap their heads around.…
Image by Author
# Introduction
You have written your first Python function. It works. You run it, it produces the right output, and you feel that rush of accomplishment. Then you look at it again two weeks later and think: “What does this even do?”
Writing readable Python functions is not about being smart or advanced. It is about writing functions that communicate their intent clearly, handle their responsibilities cleanly, and make the next person’s job easier.
Let’s learn how to write functions that read like good prose, not cryptic puzzles that readers find difficult to wrap their heads around.
🔗 You can find the code on GitHub.
# 1. Name Your Functions Like You’re Explaining to a Friend
Function names are the first thing people read. A good name tells you exactly what the function does without requiring you to read the code inside.
Bad example:
def proc(d):
return sum(d) / len(d)
Good example:
def calculate_average(numbers):
return sum(numbers) / len(numbers)
The function name calculate_average uses a verb (“calculate”) that tells you it performs an action, followed by what it calculates (“average”). The parameter numbers clearly indicates it expects a collection of numerical values.
The function body uses sum(numbers) to add all values and len(numbers) to count them, then divides to get the average. Anyone reading this code instantly understands what it does without needing comments or documentation.
# 2. Use Descriptive Parameter Names
Single-letter variables might save typing, but they cost understanding. Parameters are your function’s inputs, so make it obvious what you expect.
Bad example:
def discount(p, r):
return p * (1 - r)
Good example:
def apply_discount(original_price, discount_rate):
return original_price * (1 - discount_rate)
Now anyone reading apply_discount(100, 0.2) understands you are taking 20% off 100. The code is self-documenting. You do not need to check the function definition to understand what arguments to pass.
# 3. Keep Functions Short and Focused
Functions should do one thing well. If your function has multiple responsibilities, it becomes hard to test, reuse, and understand. So, break complex logic into smaller, focused functions.
Bad example:
def process_order(items, customer_email, discount_code):
# Calculate total
subtotal = sum(item["price"] * item["quantity"] for item in items)
# Apply discount
if discount_code == "SAVE10":
discount = 0.10
elif discount_code == "SAVE20":
discount = 0.20
else:
discount = 0
total = subtotal * (1 - discount)
# Send email
subject = f"Order Confirmation"
body = f"Your order total is ${total:.2f}"
send_email(customer_email, subject, body)
return total
Good example:
def calculate_order_subtotal(items):
return sum(item["price"] * item["quantity"] for item in items)
def get_discount_rate(discount_code):
discount_rates = {"SAVE10": 0.10, "SAVE20": 0.20}
return discount_rates.get(discount_code, 0)
def apply_discount_to_subtotal(subtotal, discount_rate):
return subtotal * (1 - discount_rate)
def send_order_confirmation_email(customer_email, total):
subject = "Order Confirmation"
body = f"Your order total is ${total:.2f}"
send_email(customer_email, subject, body)
def process_order(items, customer_email, discount_code):
subtotal = calculate_order_subtotal(items)
discount_rate = get_discount_rate(discount_code)
total = apply_discount_to_subtotal(subtotal, discount_rate)
send_order_confirmation_email(customer_email, total)
return total
Now each function has a single, clear purpose. The main process_order function reads like a recipe: calculate subtotal, get discount, apply it, send email, return total.
# 4. Add Docstrings to Explain Purpose
Function names tell you what a function does, but docstrings explain why it exists, what it expects, and what it returns. This is especially helpful for complex logic or non-obvious behavior.
Good example:
def calculate_shipping_cost(weight_kg, distance_km, is_express=False):
"""
Calculate shipping cost based on package weight and distance.
Args:
weight_kg (float): Package weight in kilograms
distance_km (float): Shipping distance in kilometers
is_express (bool): Whether to use express shipping (default: False)
Returns:
float: Total shipping cost in dollars
Example:
>>> calculate_shipping_cost(5.0, 100, is_express=True)
45.50
"""
base_rate = 2.50
per_kg_rate = 1.20
per_km_rate = 0.15
express_multiplier = 2.0 if is_express else 1.0
cost = (
base_rate + (weight_kg * per_kg_rate) + (distance_km * per_km_rate)
) * express_multiplier
return round(cost, 2)
The docstring explains what each parameter means, what type it should be, and what the function returns. Anyone using this function knows exactly how to call it without reading the implementation.
# 5. Use Clear Variable Names Inside Functions
Just like parameters, internal variables should be descriptive, too. Do not make people decode abbreviations or guess what tmp or x represents.
Bad example:
def calc_bmi(w, h):
h_m = h / 100
res = w / (h_m**2)
return round(res, 1)
Good example:
def calculate_bmi(weight_kg, height_cm):
height_meters = height_cm / 100
bmi = weight_kg / (height_meters**2)
return round(bmi, 1)
The variable height_meters tells you exactly what conversion happened. And as seen, the variable bmi holds the body mass index (BMI) calculation.
# 6. Avoid Magic Numbers; Use Named Constants Instead
Numbers scattered through your code are “magic”, meaning their purpose is unclear. Give them meaningful names so readers understand their significance.
Bad example:
def calculate_late_fee(days_overdue):
if days_overdue <= 7:
return days_overdue * 2
else:
return 14 + (days_overdue - 7) * 5
Good example:
def calculate_late_fee(days_overdue):
DAILY_FEE_FIRST_WEEK = 2
GRACE_PERIOD_DAYS = 7
BASE_FEE_AFTER_GRACE = 14
DAILY_FEE_AFTER_GRACE = 5
if days_overdue <= GRACE_PERIOD_DAYS:
return days_overdue * DAILY_FEE_FIRST_WEEK
else:
days_after_grace = days_overdue - GRACE_PERIOD_DAYS
return BASE_FEE_AFTER_GRACE + (days_after_grace * DAILY_FEE_AFTER_GRACE)
Now the fee structure is obvious. The constants document your business rules. When rates change, you update one clearly-named value instead of searching for mysterious numbers.
# 7. Use Type Hints for Clarity
Type hints tell readers what types your function expects and returns. This prevents confusion and catches bugs early. It is good practice to add type hints in your functions.
Good example:
def format_user_greeting(user_name: str, age: int, is_member: bool = False) -> str:
membership_status = "member" if is_member else "guest"
return f"Hello {user_name}, age {age}. You are a {membership_status}."
The type hints make it clear: user_name is a string, age is an integer, is_member is a Boolean with a default of False, and the function returns a string. Your integrated development environments (IDEs) can use this information to provide better autocomplete and error checking.
# Conclusion
Readable functions are not harder to write. They just require thinking about your user. Every choice you make — names, structure, comments — either helps or hinders understanding.
The goal is not perfect code. It is code that communicates clearly. Code that makes the next person say “ah, I get it” instead of “what is this doing really?” That is readable code, and you can write it from day one.
In the next article, we will learn how to write clean Python classes. Until then, keep coding!
Bala Priya C is a developer and technical writer from India. She likes working at the intersection of math, programming, data science, and content creation. Her areas of interest and expertise include DevOps, data science, and natural language processing. She enjoys reading, writing, coding, and coffee! Currently, she’s working on learning and sharing her knowledge with the developer community by authoring tutorials, how-to guides, opinion pieces, and more. Bala also creates engaging resource overviews and coding tutorials.