10 Python Anti-Patterns: Stop Writing Over-Engineered Chaos

The Trap of Clever Python Code

often rewards cleverness, but that flexibility serves as a double-edged sword. Developers frequently stumble into patterns that look sophisticated yet cripple long-term maintainability. These are anti-patterns: solutions that seem effective in the moment but create friction as a codebase scales. Writing clean code isn't about using every feature the language offers; it's about knowing when to keep things simple. From misusing exceptions to over-engineering with unnecessary design patterns, let's break down the habits that are likely hurting your projects.

Control Flow and Structural Missteps

A common habit is using exceptions for control flow. This stems from the

philosophy that it is "easier to ask for forgiveness than permission" (EAFP). However, nesting try-except blocks to handle standard logic—like switching to a backup API—makes code unreadable and slow. Exceptions are for exceptional circumstances, not for expected logical branches.

Similarly, many developers coming from

backgrounds wrap utility functions in classes containing only static methods. This is unnecessary. In
Python
, modules are the natural namespace. If a class doesn't maintain state or provide an instance, it should just be a collection of functions in a .py file.

# Anti-pattern: Using classes for simple functions
class DataUtils:
    @staticmethod
    def clean_text(text):
        return text.strip().lower()

# Better: Just use a module-level function
def clean_text(text):
    return text.strip().lower()
10 Python Anti-Patterns: Stop Writing Over-Engineered Chaos
10 Python Anti-Patterns That Are Breaking Your Code

The Dangers of Surprising Behavior

Overriding dunder methods—like __new__—to return unexpected object types is a recipe for debugging nightmares. If you call an initializer for a Payment class, you expect a Payment object, not a sub-instance of StripePayment or PayPalPayment. This "magic" breaks the expectations of linters and teammates alike. Instead of complex inheritance magic, use a simple dictionary or an

to map types to specific functions.

We also see this with custom decorators used solely for dependency injection. Wrapping a main function in a three-layer-deep nested callable just to pass a configuration file adds immense cognitive load. Often, a direct function call to load_config() is the cleaner, more transparent choice.

Over-Engineering and Inappropriate Intimacy

Design patterns like the Abstract Factory or Visitor pattern are powerful tools, but they often lead to over-engineering. If a dictionary and two functions can solve the problem, creating five classes and an abstract base class is a waste of time. The most important principle is keeping it simple.

Furthermore, keep an eye out for Inappropriate Intimacy, where two classes know too much about each other’s internal structures. If an exporter function has to know the exact attribute names of a Report object, they are too tightly coupled.

# Improved Abstraction with Protocols
from typing import Protocol

class Exportable(Protocol):
    def to_csv(self) -> str: ...

def export_to_csv(item: Exportable):
    print(item.to_csv())

By using

, you decouple the logic. The exporter doesn't care if it's handling a Report or a Budget, as long as the object follows the contract.

Prerequisites

To get the most out of these refactoring techniques, you should be comfortable with basic

syntax, the concept of classes and inheritance, and how modules function as namespaces. Familiarity with decorators and type hints will help in understanding the more advanced structural changes.

Key Libraries & Tools

  • Python Standard Library: Specifically os.walk, json, and typing.Protocol for structural integrity.
  • Lokalise: An AI-powered platform used to manage translations and move hardcoded strings out of the UI.
  • Pydantic / Click / Typer: Mature libraries that replace the need for custom-built boilerplate for data validation and CLI interfaces.

Syntax Notes

  • Dunder Methods: Special methods like __call__ allow objects to be treated as functions, which can be a cleaner alternative to complex decorators.
  • Type Annotations: While ignored at runtime, these are vital for static analysis and making code self-documenting.
  • Wildcard Imports: Using from module import * is an anti-pattern because it pollutes the namespace and obscures the source of functions.

Practical Examples

Instead of hardcoding every UI string in a

app, which makes internationalization impossible, use a translation key system. By calling a translation object, you can switch the entire app's language—from English to Dutch, for example—by changing a single environment variable.

Tips & Gotchas

Avoid the urge to "reinvent the wheel."

is famous for having "batteries included." Before writing a recursive file walker, check if os.walk already exists. The ecosystem is your greatest asset; leverage mature third-party libraries rather than building custom, unproven solutions for standard problems.

4 min read