(Collection of examples of anti-patterns in Python)
Understanding and avoiding anti-patterns is crucial for writing clean, maintainable, and efficient Python code.
with
ensures proper file closing even if exceptions occur, preventing resource leaks or data corruption.- Skipping
with
requires manual closing, prone to error.
def bad_open():
file = open("myfile.txt", "r")
content = file.read()
file.close()
# --- vs ---
def good_open():
with open("myfile.txt", "r") as file:
content = file.read()
- While powerful, using them for simple tasks adds complexity and reduces readability.
- Consider simpler loops or explicit variable assignments for clarity.
filtered_users = {user['name']: user for user in users if user['age'] > 18}
# This comprehension builds a dictionary based on age filtering, but it's less clear than:
filtered_users = {}
for user in users:
if user['age'] > 18:
filtered_users[user['name']] = user
- Generators are memory-efficient for large datasets, but not always necessary for small ones.
- Using them for simple iterations without performance concerns adds unneeded complexity.
def count_letters(text):
return sum(1 for char in text if char.isalpha()) # Generator for simple counting
# --- vs ---
def count_letters(text):
count = 0
for char in text:
if char.isalpha():
count += 1
return count # Direct counter
- Makes code harder to understand and reuse.
- Use namedtuples, return dictionaries, or separate functions for clarity and flexibility.
def get_user_info(user_id):
if user_id in active_users:
return active_users[user_id] # Returns a dictionary
else:
return None # Returns NoneType
# This function might return a dictionary or None, making it less predictable and harder to use.
def get_user_info(user_id):
user = active_users.get(user_id) # Use .get() for consistent return type
return user or {} # Return an empty dictionary if user not found
- Checking key existence and assigning a default value is verbose and inefficient.
dict.get(key, default)
provides a clean, one-line solution.
data = {"name": "Alice"}
if "age" in data:
age = data["age"]
else:
age = 30 # Hardcoded default value
# --- vs ---
data = {"name": "Alice"}
age = data.get("age", 30) # Default value with `get()`
- Accessing keys and values separately with indexing is less efficient than the built-in
items()
method. for key, value in my_dict.items():
simplifies iteration and avoids indexing errors.
user = {"name": "Alice", "age": 30}
for key in user:
value = user[key]
# Process key and value
# --- vs ---
user = {"name": "Alice", "age": 30}
for key, value in user.items():
# Process key and value
- Debuggers slow down performance and reveal sensitive information in production environments.
- Use logging or remote debugging tools for production troubleshooting.
def my_function():
critial_data = get_critical_data()
process_data(critial_data)
print(critial_data) # Debugger statement
# --- vs ---
import logging
def my_function():
critial_data = get_critical_data()
process_data(critial_data)
clean_data = process_data(critial_data)
location = save_artifact(clean_data)
logging.info(f"Processed data: {clean_data.id} @ {location}") # Logging statement
- Classes containing hundreds of lines and methods become difficult to understand, maintain, and test.
- This leads to poor maintainability, testing challenges, and potential coupling issues.
- Single Responsibility Principle (SRP) Break down the functionalities into smaller, focused classes responsible for specific tasks.
- Composition and inheritance Create a hierarchy of classes where smaller classes inherit or compose to achieve desired functionality.
- Modular design Organize code into modules or packages for logical grouping and improved reusability.
"It might seem impressive at first, but like a house of cards, one small change can bring the whole thing tumbling down. Refactoring becomes a religious experience."
- Hardcoded values throughout code make it inflexible and error-prone.
- Use constants or configuration files to make them easier to change and track.
# Use with clear constants:
def calculate_discount(price, discount_threshold=10):
if price > discount_threshold: # Hardcoded magic number
return price * 0.8 # Another magic number
else:
return price
# --- vs ---
DISCOUNT_THRESHOLD = 10 # Defined constant
DISCOUNT_RATE = 0.8 # Defined constant
def calculate_discount(price):
if price > DISCOUNT_THRESHOLD:
return price * DISCOUNT_RATE
else:
return price
✨ Bonus for reading all of this! ✨
- Copying code without understanding its purpose or functionality.
- Leads to bloated, inefficient, and error-prone code.
- Always understand and adapt code to your specific needs.
- Seek to understand the "why" behind the code, not just the "how"
- Blindly copying code from Stack Overflow without understanding its implications.
- Using complex libraries or frameworks for simple tasks without understanding their internals.
Read more: Cargo cult programming