PEP 8 is the official style guide for Python code. PEP stands for Python Enhancement Proposal, and PEP 8 was written to give the Python community a shared set of formatting conventions. When every project follows the same rules for indentation, line length, whitespace, and naming, Python developers can move between codebases without needing to adjust to a new visual style each time. The guide was originally authored by Guido van Rossum, Python's creator, along with Barry Warsaw, and has been updated over the years as the language evolved.
Following PEP 8 is not about being pedantic. It is about reducing the mental effort of reading code. When every file uses the same indentation and whitespace patterns, your brain stops noticing the formatting and starts focusing on the logic. PEP 8 extends that consistency to every visual aspect of your code.
Indentation: 4 spaces per level
The most fundamental PEP 8 rule: use 4 spaces for each indentation level. This applies to function bodies, loop bodies, if blocks, class definitions, and every other block in Python.
def calculate_total(items, tax_rate):
subtotal = sum(items)
if subtotal > 0:
tax = subtotal * tax_rate
return subtotal + tax
return 0Do not use tabs. Configure your editor to insert spaces when you press the Tab key. Most Python-aware editors do this by default.
For line continuations inside parentheses, brackets, or braces, PEP 8 offers two styles. The hanging indent with a trailing comma is the more common choice in modern Python codebases because it produces cleaner diffs when arguments are added or removed:
# Hanging indent with trailing comma (preferred by many teams)
result = some_function(
first_argument,
second_argument,
third_argument,
)Line length: 79 characters for code, 72 for docstrings
PEP 8 recommends keeping lines at or under 79 characters. This limit persists because shorter lines are easier to read and compare side by side in diffs. For docstrings and comments, the limit is 72 characters because docstrings are often indented inside a function or class body.
def process_records(filepath, encoding="utf-8"):
"""Read records from a file and return them as a list of dictionaries.
Each line in the file is expected to be a JSON object. Empty lines
and lines starting with # are skipped.
"""
records = []
with open(filepath, encoding=encoding) as fh:
for line in fh:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
records.append(json.loads(stripped))
return recordsWhen a line is too long, put the expression inside parentheses and let implicit line continuation handle the break. Use a backslash only as a last resort for with statements and assert statements.
Blank lines: separate top-level definitions
Use two blank lines before top-level function and class definitions. Use one blank line before method definitions inside a class. Use blank lines sparingly inside functions to separate logical sections.
import os
import sys
def load_config():
"""Read configuration from the default config file."""
path = os.path.expanduser("~/.myapp/config.toml")
return _parse_config(path)
def _parse_config(path):
"""Internal helper to parse a TOML config file."""
with open(path) as fh:
return toml.load(fh)Inside a class, use a single blank line between methods. The two blank lines between top-level definitions create clear visual separation:
class ConfigManager:
"""Manages application configuration with caching."""
def __init__(self):
self._cache = {}
def get(self, key, default=None):
if key not in self._cache:
self._cache[key] = self._load_key(key)
return self._cache.get(key, default)
def _load_key(self, key):
"""Internal: load a single config key from disk."""
return self._reader.read(key)Imports: one per line, grouped and sorted
Each import should be on its own line. Organize imports in three groups separated by a blank line: standard library imports, third-party imports, and local application imports. Alphabetize within each group.
# Standard library
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
# Third-party
import requests
from pydantic import BaseModel
# Local
from myapp.config import settings
from myapp.utils import format_dateAvoid wildcard imports (from module import *). They pollute the namespace and make it impossible to trace where a name came from.
Whitespace around operators and after commas
Place a single space on each side of assignment operators, comparison operators, and arithmetic operators. Do not add spaces around = in keyword arguments or default parameter values. Do not add a space immediately inside parentheses, brackets, or braces.
# Correct spacing around operators
x = 5
total = price * quantity
result = (a + b) * (c - d)
items = [1, 2, 3]
# No spaces around = in keyword arguments
response = requests.get(url, timeout=10)
# No spaces inside brackets
values = [1, 2, 3]
point = (x, y)
mapping = {"key": "value"}For slices, treat the colon as a binary operator with equal spaces on each side when both start and end are present. When one side is omitted, omit the space on that side:
items[1:4] # space on both sides of colon
items[:4] # no space before colon
items[1:] # no space after colon
items[:] # no spaces
items[1:4:2] # step also gets spacesNaming conventions
PEP 8 defines a clear naming scheme for every kind of Python identifier. Consistent naming makes it obvious at a glance whether a name refers to a function, a class, or a constant.
| Kind | Convention | Example |
|---|---|---|
| Functions | snake_case | calculate_total, fetch_user |
| Variables | snake_case | user_count, max_retries |
| Classes | PascalCase | UserAccount, HttpClient |
| Constants | UPPER_CASE | MAX_CONNECTIONS, DEFAULT_PORT |
| Methods | snake_case | get_balance, set_name |
| Modules | snake_case | data_processor.py, email_utils.py |
| Packages | short lowercase | mypackage, utils |
| Private internals | _leading_underscore | _cache, _parse_config |
Private names start with a single leading underscore. This is a convention, not a language-level access restriction. It tells other developers that the name is an implementation detail. For the reasoning behind each convention in this table, see Name Python Variables, Functions, and Classes.
Avoid single-letter names except in short loops and comprehensions where the meaning is obvious from context. Never use lowercase l, uppercase O, or uppercase I as single-character names because they are easily confused with the digits 1 and 0.
Comments: explain why, not what
Write comments in complete sentences with a space after the #. The comment should explain the reasoning behind non-obvious code, not restate what the code already says.
# Bad: restates the code
x = x + 1 # increment x
# Good: explains the decision
x = x + 1 # compensate for the 0-based index in the external APIDocstrings are triple-quoted strings that describe modules, classes, and functions. PEP 257 defines the docstring conventions that complement PEP 8.
Automate formatting with Ruff and Black
You do not need to check every PEP 8 rule manually while coding. Python has mature formatting tools that apply the rules automatically.
Ruff is a fast Python linter and formatter written in Rust. It enforces hundreds of rules including all of PEP 8 and can auto-fix most violations:
pip install ruff
ruff check . # check for issues
ruff check --fix . # auto-fix what can be fixed
ruff format . # format code consistentlyBlack is a popular opinionated formatter that takes PEP 8 and applies it strictly with a few intentional deviations, such as preferring double quotes and a default line length of 88 characters.
Both tools integrate into your editor to format on save. A typical modern Python project uses Ruff for both linting and formatting. PEP 8 covers layout and formatting, but idiomatic Python goes further; see Write Pythonic Code in Python for the patterns that come next. Configure Ruff in pyproject.toml:
[tool.ruff]
line-length = 88
[tool.ruff.format]
quote-style = "double"This setup catches violations as you type and fixes them on save. You spend zero mental energy on whitespace and can focus entirely on the logic.
Rune AI
Key Insights
- PEP 8 is the official Python style guide covering indentation, line length, whitespace, imports, and naming.
- Use 4 spaces per indentation level and keep lines at or under 79 characters where practical.
- Organize imports in three groups: standard library, third-party, and local.
- Use snake_case for functions and variables, PascalCase for classes, and UPPER_CASE for constants.
- Automate formatting with Ruff or Black so you do not need to think about spacing during development.
Frequently Asked Questions
What is PEP 8?
Do I need to memorize every PEP 8 rule?
Conclusion
PEP 8 is not about making every Python file look identical. It is about removing the cognitive friction of inconsistent formatting so readers can focus on what the code does. Learn the rules, automate enforcement, and spend your mental energy on logic instead of layout.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.