Stop writing Messy Python: The crash course of a clean code

Photo by writer | Ideogram
If you have put codes in the Python for a while, you may well know the foundations, build a few projects. And now you are looking for your code to think: “This works, but … it's not what I can proudly show about the code review. We're all there.
But as you save codes, writing clean code becomes a very important thing as the writing code. In this article, I have integrated practical strategies that can help you travel from “running, and do not be kept. “
🔗 Link to the code in Githubub
1. Model Past is clearly. Don't pass around around
Dictionaries are flexible with python and is the problem. When you pass the green dictionary throughout your code, inviting Typos, key errors, and confusion about which data should be.
Instead of:
def process_user(user_dict):
if user_dict['status'] == 'active': # What if 'status' is missing?
send_email(user_dict['email']) # What if it's 'mail' in some places?
# Is it 'name', 'full_name', or 'username'? Who knows!
log_activity(f"Processed {user_dict['name']}")
This code is not strong because it takes dictionary buttons are without verification. Does not provide protection from typos or lined keys, which will create KeyError
different at the time to start. None of the documents of what sectors are expected.
Do this:
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
id: int
email: str
full_name: str
status: str
last_login: Optional[datetime] = None
def process_user(user: User):
if user.status == 'active':
send_email(user.email)
log_activity(f"Processed {user.full_name}")
Python's @dataclass
Decorations to give a clean, clear of a small boeelplate. Your ide is now able to automatically provide qualifications, and you will find errors immediately if the required fields are lost.
MORE CONCLUSION MORE, Consider the Pydantic:
from pydantic import BaseModel, EmailStr, validator
class User(BaseModel):
id: int
email: EmailStr # Validates email format
full_name: str
status: str
@validator('status')
def status_must_be_valid(cls, v):
if v not in {'active', 'inactive', 'pending'}:
raise ValueError('Must be active, inactive or pending')
return v
Now your data is compatible with it, it holds shortcomes in advance, as well as clear-expected documents.
2. Use ENUMS for known options
String is stretched strings tend to typos and provide exception of EDOCOCOMMPLETE EDO. Confirmation happens only during the start time.
Instead of:
def process_order(order, status):
if status == 'pending':
# process logic
elif status == 'shipped':
# different logic
elif status == 'delivered':
# more logic
else:
raise ValueError(f"Invalid status: {status}")
# Later in your code...
process_order(order, 'shiped') # Typo! But no IDE warning
Do this:
from enum import Enum, auto
class OrderStatus(Enum):
PENDING = 'pending'
SHIPPED = 'shipped'
DELIVERED = 'delivered'
def process_order(order, status: OrderStatus):
if status == OrderStatus.PENDING:
# process logic
elif status == OrderStatus.SHIPPED:
# different logic
elif status == OrderStatus.DELIVERED:
# more logic
# Later in your code...
process_order(order, OrderStatus.SHIPPED) # IDE autocomplete helps!
If you are facing a fixed set of options, ENUM makes your code stronger and have a test.
With eNums:
- Your ide is providing automatic suggestions
- Typos become (approximately) impossible
- You can in every price possible when required
ENUM creates a collection of combined objects. Icon of type status: OrderStatus
Documents type parameter is expected. Use OrderStatus.SHIPPED
Instead of a literal thread allows an autoComplete Idel and holds Typos during development.
3. Use only keywords clear
The Python system variables the system is powerful, but it can lead to confusion when Work calls have many options packages.
Instead of:
def create_user(name, email, admin=False, notify=True, temporary=False):
# Implementation
# Later in code...
create_user("John Smith", "[email protected]", True, False)
Wait, what do those booleans mean and?
When called the entrack, it is not clear what the boolean prices represent without regard for the operation. True with treatment, notify, or something else?
Do this:
def create_user(name, email, *, admin=False, notify=True, temporary=False):
# Implementation
# Now you must use keywords for optional args
create_user("John Smith", "[email protected]", admin=True, notify=False)
*, Syntax will force all conflicts after the keyword. This makes your work calling and preventing the problem of “boolean mystery” when students can determine whether that is true or false by reading the work description.
This method is very useful for APIs calls and more, where you want to confirm the clarification on the call site.
4. Use Pathlib on top of OS.PATH
Os.path Path of Python is active but Clunky. The new pathlib module provides a prioristic manner and has little errors.
Instead of:
import os
data_dir = os.path.join('data', 'processed')
if not os.path.exists(data_dir):
os.makedirs(data_dir)
filepath = os.path.join(data_dir, 'output.csv')
with open(filepath, 'w') as f:
f.write('resultsn')
# Check if we have a JSON file with the same name
json_path = os.path.splitext(filepath)[0] + '.json'
if os.path.exists(json_path):
with open(json_path) as f:
data = json.load(f)
This uses the string of the string with os.path.join()
including os.path.splitext()
by treating the way. The performance of how to disperse with different activities. Code is Vermase and accurate.
Do this:
from pathlib import Path
data_dir = Path('data') / 'processed'
data_dir.mkdir(parents=True, exist_ok=True)
filepath = data_dir / 'output.csv'
filepath.write_text('resultsn')
# Check if we have a JSON file with the same name
json_path = filepath.with_suffix('.json')
if json_path.exists():
data = json.loads(json_path.read_text())
Why is pathlib is better:
- How to join with / more accurate
- Ways such as
mkdir()
,exists()
besideread_text()
are attached to the object object - Performance such as transforming extensions (with a_affix) is semantics
Pathlib deals trickling trick How to work in practical programs. This makes your code more affected and strong.
5. Fail quickly with Guard Clauses
The statements are well organized when it is often difficult to understand and save. Using early back – monitor phrases – resulting in a more readable code.
Instead of:
def process_payment(order, user):
if order.is_valid:
if user.has_payment_method:
payment_method = user.get_payment_method()
if payment_method.has_sufficient_funds(order.total):
try:
payment_method.charge(order.total)
order.mark_as_paid()
send_receipt(user, order)
return True
except PaymentError as e:
log_error(e)
return False
else:
log_error("Insufficient funds")
return False
else:
log_error("No payment method")
return False
else:
log_error("Invalid order")
return False
The deep nest is hard to follow. Each conditional block needs to track many branches at the same time.
Do this:
def process_payment(order, user):
# Guard clauses: check preconditions first
if not order.is_valid:
log_error("Invalid order")
return False
if not user.has_payment_method:
log_error("No payment method")
return False
payment_method = user.get_payment_method()
if not payment_method.has_sufficient_funds(order.total):
log_error("Insufficient funds")
return False
# Main logic comes after all validations
try:
payment_method.charge(order.total)
order.mark_as_paid()
send_receipt(user, order)
return True
except PaymentError as e:
log_error(e)
return False
Choisers Clashes deals with the errors that are facing earlier, reducing teaching standards. Each situation is checked in a row, making the flow easy to follow. The main logic comes in the end, clearly divided into handling error.
This approach measures very better as your logic grows with difficulty.
6. Don't override to understand
The list storage is one of the best Python things, but they are not readable when they are loaded highly by complex circumstances or changes.
Instead of:
# Hard to parse at a glance
active_premium_emails = [user['email'] for user in users_list
if user['status'] == 'active' and
user['subscription'] == 'premium' and
user['email_verified'] and
not user['email'] in blacklisted_domains]
This listing list is very parked in one line. It is difficult to study and to correct an error. Many situations are tied together, making it difficult to understand sorting methods.
Do this:
Here are some better ways.
Option 1: Work with descriptive name
Produce a complex situation to work whose name is a descriptive word. The recognition of the list now is clear, focusing on what you do (to remove e-emails) rather than sort.
def is_valid_premium_user(user):
return (user['status'] == 'active' and
user['subscription'] == 'premium' and
user['email_verified'] and
not user['email'] in blacklisted_domains)
active_premium_emails = [user['email'] for user in users_list if is_valid_premium_user(user)]
Option 2: Traditional LOOP when Logic is complicated
Uses traditional loop with clarity. Each situation is inserted separately, making it easier to repair the situation that may be failing. Transformation Logic is also clearly divided.
active_premium_emails = []
for user in users_list:
# Complex filtering logic
if user['status'] != 'active':
continue
if user['subscription'] != 'premium':
continue
if not user['email_verified']:
continue
if user['email'] in blacklisted_domains:
continue
# Complex transformation logic
email = user['email'].lower().strip()
active_premium_emails.append(email)
Listing stability should make your code more readable, not below. When logic becomes complicated:
- Break out complex situations in the names of name
- Consider using regular loop with continuing
- Divide complex tasks into many steps
Remember, goal is to read.
7. Write the practical pure activities
Work is a pure job when producing the same effect of the same installation. Also, they do not have side effects.
Instead of:
total_price = 0 # Global state
def add_item_price(item_name, quantity):
global total_price
# Look up price from global inventory
price = inventory.get_item_price(item_name)
# Apply discount
if settings.discount_enabled:
price *= 0.9
# Update global state
total_price += price * quantity
# Later in code...
add_item_price('widget', 5)
add_item_price('gadget', 3)
print(f"Total: ${total_price:.2f}")
This is using global conditions (total_price
) Why Exercise It's Hard.
An employee has side effects (changing the international country) and depends on the external form of (inventory and settings). This makes it unpredictable and difficult to use.
Do this:
def calculate_item_price(item, price, quantity, discount=0):
"""Calculate final price for a quantity of items with optional discount.
Args:
item: Item identifier (for logging)
price: Base unit price
quantity: Number of items
discount: Discount as decimal
Returns:
Final price after discounts
"""
discounted_price = price * (1 - discount)
return discounted_price * quantity
def calculate_order_total(items, discount=0):
"""Calculate total price for a collection of items.
Args:
items: List of (item_name, price, quantity) tuples
discount: Order-level discount
Returns:
Total price after all discounts
"""
return sum(
calculate_item_price(item, price, quantity, discount)
for item, price, quantity in items
)
# Later in code...
order_items = [
('widget', inventory.get_item_price('widget'), 5),
('gadget', inventory.get_item_price('gadget'), 3),
]
total = calculate_order_total(order_items,
discount=0.1 if settings.discount_enabled else 0)
print(f"Total: ${total:.2f}")
The next version uses pure activities that take all the dependence on like parameters.
8. Write public activity documents and classes
The Scriptures are not (and should not) then. An important part of the final code. Good Doctrings do not specify what tasks do, but why exists and how to use them well.
Instead of:
def celsius_to_fahrenheit(celsius):
"""Convert Celsius to Fahrenheit."""
return celsius * 9/5 + 32
This is a small docstring repeating the work name only. It provides information about parameters, return prices, or edge cases.
Do this:
def celsius_to_fahrenheit(celsius):
"""
Convert temperature from Celsius to Fahrenheit.
The formula used is: F = C × (9/5) + 32
Args:
celsius: Temperature in degrees Celsius (can be float or int)
Returns:
Temperature converted to degrees Fahrenheit
Example:
>>> celsius_to_fahrenheit(0)
32.0
>>> celsius_to_fahrenheit(100)
212.0
>>> celsius_to_fahrenheit(-40)
-40.0
"""
return celsius * 9/5 + 32
Good Doctring:
- Parameters of documents and return prices
- Notes any other different can be awakened
- Provides examples of use
Your documents act as visible texts that are always synced with your code.
9. Automatically make formatting and formatting
Do not rely on hand tests to catch style stories and regular bugs. Automatic tools can manage a heated work to ensure code and agree.
You can try to set these formatting tools and formatting:
- Dark – The format code
- Fuffe – Fast Lterter
- gotten – Static Type Type Checker
- isort – Central Editor
Mix using configuration hooks to automatically check and format code before each commitment:
- Enter Pre-Off: Code Style = “Background: # F5f5f5;”> PIP Install Pre-Ourn
- Create code style = “Background: # F5f5f5;”>. The File Pre-Commitment.Yaml and Tools
- Code Style = “Background: # F5f5f5;”> Previous installation to activate
This setup guarantees the fixed code style and holds errors before the hands of hand.
You can check 7 tools to help write a better Python Code to know more about this.
10. Avoid holding everything out
Generic administrators who removes they hide bugs and make adjustments. They catch everything, including syntax errors, memory errors, and keyboard disorders.
Instead of:
try:
user_data = get_user_from_api(user_id)
process_user_data(user_data)
save_to_database(user_data)
except:
# What failed? We'll never know!
logger.error("Something went wrong")
This is a different manner of administration:
- Syntax errors)
- System errors (such as Mentererror)
- Keyboard interferes (CTRL + c)
- Unexpected Mistakes (such as Network Timeouts)
This makes the defense very difficult, as all errors are treated the same.
Do this:
try:
user_data = get_user_from_api(user_id)
process_user_data(user_data)
save_to_database(user_data)
except ConnectionError as e:
logger.error(f"API connection failed: {e}")
# Handle API connection issues
except ValueError as e:
logger.error(f"Invalid user data received: {e}")
# Handle validation issues
except DatabaseError as e:
logger.error(f"Database error: {e}")
# Handle database issues
except Exception as e:
# Last resort for unexpected errors
logger.critical(f"Unexpected error processing user {user_id}: {e}",
exc_info=True)
# Possibly re-raise or handle generically
raise
Different holding unpredictable and properly treated. Each different kind consists of its error message and managing a plan.
Lastly without trapping unexpected mistakes, including a full track (exc_info=True
), and rehabilitate them to avoid satisfying great news.
If you need to catch all the holdings for some reason, use except Exception as e:
Instead of inadequate except:
and always enter perfect full details with exc_info=True
.
Rolling up
I hope you will use at least some of these practices in your code. Start using them in your projects.
You will find your code more better, more tested, and easy to think about.
Next time you are tempted to take a shortcut, remember: The code reads again and more times than what is written. Clean edible editing?
Count Priya c He is the writer and a technical writer from India. He likes to work in mathematical communication, data science and content creation. His areas of interest and professionals includes deliefs, data science and natural language. She enjoys reading, writing, coding, and coffee! Currently, he works by reading and sharing his knowledge and engineering society by disciples of teaching, how they guide, pieces of ideas, and more. Calculate and create views of the resources and instruction of codes.