Fundamentals 11 min read

Stop Using a Monolithic utils.py: 3 Design Patterns to Make Your Python Code Cleaner

The article explains why a sprawling utils.py becomes a maintenance nightmare in Python projects, outlines three concrete patterns—module‑level placement, class‑based methods, and small dedicated modules—to restructure code, and provides decision criteria for when a function truly belongs in a utility module.

Data STUDIO
Data STUDIO
Data STUDIO
Stop Using a Monolithic utils.py: 3 Design Patterns to Make Your Python Code Cleaner

Many Python projects accumulate a huge utils.py file that ends up as a “garbage dump” because developers keep adding unrelated helper functions. The article first shows a typical 800‑line utils.py containing date formatting, string handling, data validation, and API wrappers, and explains three hidden problems:

Problem 1: Loss of Context

Calling utils.validate_email() gives no clue whether the validation is for registration, subscription, or a contact form, mixing different scenarios in one place.

Problem 2: Tight Coupling without Awareness

The function calculate_discount hard‑codes business rules (VIP 20% off, new user 10% off). When the promotion changes, developers must hunt down every usage, making changes risky.

Problem 3: Business‑Logic Garbage Dump

Because “if you don’t know where to put it, put it in utils”, domain‑specific logic accumulates, turning apparent reuse into chaos.

Better Pattern 1 – Put Functions in Their Own Modules

Let behavior live close to its domain.

Before refactor:

from utils import format_date, validate_input
result1 = format_date(some_date)
result2 = validate_input(some_input)

After refactor:

from billing.dates import format_invoice_date
from auth.validators import validate_login_credentials
invoice_date = format_invoice_date(order_date)
is_valid = validate_login_credentials(username, password)

Each function now has a clear ownership and a descriptive name.

Better Pattern 2 – Use Class Methods or Properties for Data‑Specific Behavior

When data needs behavior, attach it to the data’s class.

Before refactor:

def is_order_refundable(order):
    if order.status != 'completed':
        return False
    if order.created_at < datetime.now() - timedelta(days=30):
        return False
    if order.has_refund_request:
        return False
    return True

if is_order_refundable(order):
    process_refund(order)

After refactor:

class Order:
    def __init__(self, status, created_at, has_refund_request=False):
        self.status = status
        self.created_at = created_at
        self.has_refund_request = has_refund_request

    @property
    def is_refundable(self):
        """Check whether the order can be refunded"""
        if self.status != 'completed':
            return False
        if self.created_at < datetime.now() - timedelta(days=30):
            return False
        if self.has_refund_request:
            return False
        return True

if order.is_refundable:
    order.process_refund()

This keeps the refund logic encapsulated in the Order class, improving cohesion and testability.

Better Pattern 3 – Small, Focused Modules Instead of a Big Toolbox

Use tiny, purpose‑built modules rather than a monolithic utils package.

Bad structure:

project/
├── utils.py      # 2000 lines, everything
├── helpers.py    # another catch‑all
└── common.py    # third dump

Recommended structure:

project/
├── core/
│   ├── validators.py   # data validation
│   ├── formatters.py   # data formatting
│   └── exceptions.py   # custom exceptions
├── billing/
│   ├── calculator.py   # billing calculations
│   └── formatters.py   # billing‑specific formatting
├── users/
│   ├── validators.py   # user‑related validation
│   └── services.py     # user services
└── utils/
    ├── date_utils.py   # pure date helpers
    └── string_utils.py # pure string helpers

Only truly generic, stateless functions belong in utils (e.g., days_between, format_duration, truncate).

When Is a Utility Function Appropriate?

A function can stay in a utility module if it is genuinely generic, stateless, and unrelated to business rules. Example:

# utils/date_utils.py
def days_between(date1, date2):
    """Calculate the number of days between two dates (pure function)"""
    return abs((date2 - date1).days)

def format_duration(seconds):
    """Format a time interval (pure function)"""
    hours = seconds // 3600
    minutes = (seconds % 3600) // 60
    return f"{hours}h {minutes}m"

# utils/string_utils.py
def truncate(text, length, suffix="..."):
    """Truncate a string (pure function)"""
    if len(text) <= length:
        return text
    return text[:length - len(suffix)] + suffix

Decision rule: if the function works unchanged in a completely different project, it is likely a true utility.

Simple Decision Flowchart

Start
  ↓
Is the function mainly operating on a specific object?
  ↓
Yes → Make it a method/property (Pattern 2)
No
  ↓
Does the function belong to a particular business domain?
  ↓
Yes → Place it in the corresponding domain module (Pattern 1)
No
  ↓
Is the function completely generic, stateless, and business‑agnostic?
  ↓
Yes → Put it in a small dedicated utility module (Pattern 3)
No → Re‑think whether the function should exist.

Final Thoughts

Utility functions are like instant noodles—quick and convenient but nutritionally poor if overused. Good code design resembles city planning: related pieces stay together, roads have clear destinations, and each building has a defined purpose. By applying the three principles—keep behavior near its data, replace vague toolboxes with clear module structures, and reserve utilities for truly generic code—you can dramatically improve readability and maintainability.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design Patternsmodularizationrefactoringcode organizationutils
Data STUDIO
Written by

Data STUDIO

Click to receive the "Python Study Handbook"; reply "benefit" in the chat to get it. Data STUDIO focuses on original data science articles, centered on Python, covering machine learning, data analysis, visualization, MySQL and other practical knowledge and project case studies.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.