PEP 8 in Python is the best-known style guide for writing readable and consistent code. It does not change what a program can do, but it helps teams understand, review, test, and maintain the program more easily.
This beginner guide explains naming conventions, indentation, line length, imports, whitespace, blank lines, comments, functions, classes, comparisons, exceptions, and automated formatting tools. You will also refactor a poorly formatted example into cleaner Python.
PEP 8 works best when combined with good docstrings, type hints, and automated tests.
What Is a PEP?
PEP means Python Enhancement Proposal. PEPs document standards, design ideas, processes, and information for the Python community. PEP 8 is titled “Style Guide for Python Code.”
Read the authoritative PEP 8 document. The official Python control flow tutorial also uses conventional formatting in its examples.
Why Style Matters
Code is read more often than it is written. Consistent style reduces mental effort because readers do not need to decode a new formatting system in every file.
- Reviews focus on logic instead of formatting.
- Names communicate purpose.
- Diffs contain fewer unrelated changes.
- Tools can detect common problems automatically.
- New contributors understand the project faster.
PEP 8 is guidance, not a substitute for judgment. A project’s established convention may take priority when consistency would otherwise be broken.
Use Four Spaces for Indentation
Python uses indentation as syntax. PEP 8 recommends four spaces per indentation level.
def calculate_total(prices: list[float]) -> float:
total = 0.0
for price in prices:
if price >= 0:
total += price
return totalDo not mix tabs and spaces. Configure your editor to insert spaces when you press Tab. The Python indentation guide explains blocks and common errors.
Limit Line Length Thoughtfully
PEP 8 traditionally recommends a maximum of 79 characters for code and 72 for long comments or docstrings. Some modern teams choose a slightly larger limit, such as 88 or 100, but the important rule is to agree and apply it consistently.
Break long function calls inside parentheses:
response = client.create_report(
customer_id=customer_id,
start_date=start_date,
end_date=end_date,
include_details=True,
)Use a trailing comma in multi-line structures so formatters and version-control diffs remain clean.
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Variables | snake_case | total_price |
| Functions | snake_case | load_report() |
| Classes | CapWords | InvoiceService |
| Constants | UPPER_CASE | MAX_RETRIES |
| Modules | short snake_case | email_utils.py |
Choose names based on meaning, not data type:
# Unclear
x = 30
lst = [10, 20, 30]
# Clearer
maximum_attempts = 30
monthly_sales = [10, 20, 30]Short names are reasonable for tiny scopes, such as x and y coordinates or i in a very small mathematical loop. Descriptive names are better in business logic.
Avoid Ambiguous Names
Single letters such as lowercase L, uppercase I, and uppercase O can resemble numbers. Avoid them as variable names.
# Avoid
l = 10
O = 0
# Prefer
line_count = 10
starting_offset = 0Organize Imports
Place imports near the top of the file and group them in this order:
- Standard library imports.
- Third-party imports.
- Local application imports.
from pathlib import Path
import logging
import pandas as pd
import requests
from app.config import Settings
from app.reports import build_reportSeparate groups with blank lines. Prefer one import statement per line for ordinary imports:
import os
import sysImporting several names from the same module on one line is acceptable when it remains readable.
Avoid Wildcard Imports
# Avoid
from calculations import *
# Prefer
from calculations import calculate_tax, calculate_totalWildcard imports hide where names came from and can overwrite existing names unexpectedly.
Whitespace Around Operators
subtotal = quantity * unit_price
total = subtotal + shipping_cost
is_valid = total >= 0 and quantity > 0Avoid unnecessary alignment with many spaces because it creates fragile formatting:
# Avoid decorative alignment
a_long_name = 1
x = 2
# Prefer
long_name = 1
x = 2No Extra Spaces Inside Delimiters
# Prefer
items = ["book", "course"]
result = calculate_total(items)
record = {"name": "Maya", "active": True}
# Avoid
items = [ "book", "course" ]
result = calculate_total( items )Function Arguments and Defaults
Do not add spaces around = in keyword arguments or unannotated default values:
send_email(recipient="[email protected]", urgent=True)
def connect(timeout=10):
passWith type annotations, use spaces around the default-value equals sign:
def connect(timeout: int = 10) -> None:
passUse Blank Lines to Separate Ideas
At module level, surround top-level functions and classes with two blank lines. Inside a class, use one blank line between methods. Within a function, use blank lines sparingly to separate logical steps.
DEFAULT_TAX_RATE = 0.2
def calculate_tax(amount: float) -> float:
return amount * DEFAULT_TAX_RATE
class Invoice:
def __init__(self, subtotal: float) -> None:
self.subtotal = subtotal
def total(self) -> float:
return self.subtotal + calculate_tax(self.subtotal)Write Useful Comments
Comments should explain why a decision exists, not translate obvious code into English.
# Retry because the provider may briefly return 503 during deployment.
for attempt in range(MAX_RETRIES):
response = send_request()Avoid stale comments. Update or remove a comment whenever the related code changes. For public functions and classes, use docstrings rather than a block of ordinary comments.
Use Docstrings for Public Interfaces
def calculate_discount(total: float, rate: float) -> float:
"""Return the discount amount for a total and decimal rate."""
return total * ratePEP 257 provides docstring conventions. The Python docstrings guide covers parameters, returns, exceptions, modules, and classes.
Compare Booleans Clearly
# Prefer
if is_active:
process_account()
if not is_deleted:
show_account()
# Usually avoid
if is_active == True:
process_account()Use is None for None checks:
if result is None:
return "No result"Read the Python booleans guide for truth values and identity.
Use is not Instead of not … is
# Prefer
if value is not None:
print(value)
# Avoid
if not value is None:
print(value)The preferred form reads like normal English and reduces ambiguity.
Catch Specific Exceptions
try:
quantity = int(user_input)
except ValueError:
print("Enter a whole number.")Avoid bare except: because it catches system-exiting exceptions and makes debugging difficult. Catch the narrowest useful exception and keep the protected block small.
try:
data = file_path.read_text(encoding="utf-8")
except FileNotFoundError:
logger.error("Missing file: %s", file_path)
except PermissionError:
logger.error("Permission denied: %s", file_path)Avoid Mutable Default Arguments
This is a correctness rule commonly taught alongside style:
# Avoid
def add_item(item, items=[]):
items.append(item)
return items
# Prefer
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return itemsThe default list in the first function is reused across calls.
Keep Functions Focused
A function should have one clear responsibility. Large functions are harder to name, test, and understand.
def load_sales(path: Path) -> list[dict]:
"""Load sales records from a file."""
...
def validate_sales(records: list[dict]) -> list[dict]:
"""Return valid sales records."""
...
def summarize_sales(records: list[dict]) -> dict:
"""Aggregate sales totals by category."""
...This structure works well with automated tests.
Refactoring a Poorly Styled Example
Here is difficult-to-read code:
import os,sys
TAX=.2
def calc(x,y=True):
if y==True:return x+(x*TAX)
else:return xA cleaner version:
TAX_RATE = 0.2
def calculate_total(
subtotal: float,
include_tax: bool = True,
) -> float:
"""Return the subtotal with optional tax."""
if not include_tax:
return subtotal
tax = subtotal * TAX_RATE
return subtotal + taxThe revised code removes unused imports, uses descriptive names, follows indentation rules, adds type hints and a docstring, and avoids comparing a boolean directly with True.
Formatting and Linting Tools
Automated tools reduce debates and catch problems early:
- Black: opinionated automatic formatter.
- Ruff: fast linter and formatter with many rule sets.
- Flake8: established linting tool.
- isort: import organizer.
- Pylint: detailed static analysis.
A formatter changes layout. A linter reports style, quality, and possible correctness problems. Many projects use both.
python -m pip install ruff
ruff check .
ruff format .Review changes in version control rather than applying tools blindly to a large legacy project.
Configure the Project
Place tool settings in pyproject.toml so every developer and CI job uses the same rules:
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I"]Gradually add stricter rules as the codebase becomes consistent.
Style in Existing Projects
Do not reformat an entire codebase during an unrelated bug fix. Large formatting-only diffs hide meaningful changes and create merge conflicts. Prefer:
- formatting new code;
- cleaning files being actively changed;
- a separate, agreed formatting migration;
- automated checks to prevent new inconsistencies.
Common Mistakes
- Mixing tabs and spaces.
- Using unclear one-letter names in business logic.
- Putting several imports on one line.
- Using wildcard imports.
- Writing comments that repeat the code.
- Comparing booleans with True or False unnecessarily.
- Catching every exception with a bare
except. - Applying a formatter without reviewing project conventions.
- Treating every PEP 8 recommendation as more important than clarity.
Frequently Asked Questions
Is PEP 8 mandatory?
Python does not require it for execution. Teams adopt it to improve consistency and readability.
Does Black follow every PEP 8 rule?
Black follows a consistent style influenced by PEP 8 but makes its own formatting decisions, including a default line length.
Should all lines be shorter than 79 characters?
PEP 8 recommends 79 for code, but projects may choose another documented limit. Avoid excessively long lines regardless of the exact number.
Can I ignore a style rule?
Yes, when following it would reduce clarity or conflict with a well-established project convention. Make exceptions deliberate and limited.
Conclusion
PEP 8 helps Python code look familiar across projects. Use four-space indentation, meaningful names, organized imports, sensible line breaks, focused functions, specific exceptions, and comments that explain decisions.
Automate the mechanical parts with a formatter and linter, but keep human judgment at the center. The goal is not perfect compliance—it is code that other people can understand and safely change.






