Nine refactoring steps turn clunky Python script into clean code
Refactoring From Messy to Idiomatic
Writing code that works is only the first step. True software craftsmanship lies in making that code readable, maintainable, and idiomatic. In Python, we call this writing Pythonic code. Writing Pythonic code means using the language's built-in strengths rather than fighting against them. We want to avoid overengineering, reduce boilerplate, and write logic that feels natural.

This guide steps through how to refactor a clunky, rigid fitness tracking script into clean, beautiful Python. We will replace unnecessary classes with clean functions, implement safe resource management, and apply modern standard library tools.
Prerequisites and Toolkit
To follow this tutorial, you should understand basic Python syntax, functions, and file operations.
We will use several built-in modules to clean up our code:
- dataclasses: Simplifies class creation for data storage.
- pathlib: Provides an object-oriented approach to handling file paths.
- logging: Offers a robust way to track events instead of using print statements.
The Code Walkthrough
Let us look at how to structure our program. We start by removing a class that has no state and replacing it with focused functions. Then, we use a data class to structure our inputs and an iterator to stream data safely.
from dataclasses import dataclass, field
from datetime import datetime
from collections.abc import Iterator
from pathlib import Path
import logging
# Centralize paths as constants using Path objects
FOOD_FILE = Path("food.csv")
ACTIVITY_FILE = Path("activities.csv")
# Setup logging configuration
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
def today() -> str:
return datetime.now().strftime("%Y-%m-%d")
@dataclass
class Entry:
description: str
calories: int
date: str = field(default_factory=today)
def append_entry(file_path: Path, entry: Entry) -> None:
# Using a context manager ensures safe file closing
with open(file_path, "a") as f:
f.write(f"{entry.date},{entry.description},{entry.calories}\n")
logging.info(f"Appended entry: {entry.description} ({entry.calories} cal)")
def read_entries(file_path: Path) -> Iterator[Entry]:
# Easier to Ask Forgiveness than Permission (EAFP)
try:
with open(file_path, "r") as f:
for line in f:
parts = line.strip().split(",")
if len(parts) == 3:
yield Entry(parts[1], int(parts[2]), parts[0])
except FileNotFoundError:
logging.warning(f"{file_path} not found. Starting fresh.")
def run_day_summary(date: str) -> None:
food_list = list(read_entries(FOOD_FILE))
activity_list = list(read_entries(ACTIVITY_FILE))
# List comprehensions filter and aggregate data cleanly
food_total = sum(e.calories for e in food_list if e.date == date)
activity_total = sum(e.calories for e in activity_list if e.date == date)
net_calories = food_total - activity_total
print(f"--- Summary for {date} ---")
print(f"Food intake: {food_total} calories")
print(f"Activity burn: {activity_total} calories")
print(f"Net: {net_calories} calories")
def main() -> None:
# Clear entry point avoiding global namespace pollution
banana = Entry(description="banana", calories=100)
append_entry(FOOD_FILE, banana)
running = Entry(description="running", calories=300)
append_entry(ACTIVITY_FILE, running)
run_day_summary(today())
if __name__ == "__main__":
main()
This refactored script is modular. We replaced manual file closing with context managers. We replaced repetitive manual parsing with a reusable generator function that yields clean data objects.
Syntax and Best Practices
This refactoring highlights several Pythonic paradigms. First, we favor EAFP (Easier to Ask Forgiveness than Permission) over LBYL (Look Before You Leap). Instead of checking if a file exists before opening it, we try to read it and catch the FileNotFoundError.
Second, we use default factories in our data class. If you write date: str = today(), Python evaluates that default value exactly once when it loads the module. Every entry created thereafter gets that same frozen timestamp. By using default_factory=today, Python executes our function every single time we instantiate a new Entry.
Finally, we use an Iterator via the yield keyword. Yielding records one by one keeps memory consumption minimal, even when handling large files.
Tips and Pitfalls
Avoid creating classes that do not hold state. If a class only contains a constructor and a few methods that do not modify self, delete the class. Write pure functions instead.
Do not use print statements for operational tracking. Prints clutter standard output and make debugging difficult. Standardize on the built-in logging library to gain timestamps and severity levels automatically.
- ArjanCodes
- 14%· companies
- dataclasses
- 14%· libraries
- logging
- 14%· libraries
- pathlib
- 14%· libraries
- Python
- 14%· languages
- Other topics
- 29%

How to Write Python That Feels Like Python
WatchArjanCodes // 26:39
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!