PEP 8 is the style guide for Python code. It's not a language rule, it's a social contract: a shared set of conventions that makes Python code from different authors look and feel the same. Following it is what turns a working script into code that other people can read, review, and maintain. This lesson covers what PEP 8 actually says, where its rules come from, and the handful of judgment calls every Python developer eventually has to make.
In 2001, Guido van Rossum and Barry Warsaw wrote PEP 8 to document the conventions used in the standard library itself. The goal was simple: if every Python module looks roughly the same, a reader switching between files spends their attention on the logic, not on remembering whose brace style they're looking at. Python doesn't use braces, but the same idea applies to indentation, naming, spacing, and import order.
The guide opens with a line that gets quoted often: "A Foolish Consistency is the Hobgoblin of Little Minds." PEP 8 is a guide, not a law. When the rule and the readable thing disagree, readability wins. That said, the rule wins 95% of the time, and the exceptions are rarer than beginners expect.
Three things drive almost every PEP 8 decision:
A quick mental model for where PEP 8 fits in the broader ecosystem:
PEP 8 sits at the top. PEP 257 is its companion guide for docstrings, and PEP 20 (The Zen of Python) is the philosophical backdrop. The tools at the bottom enforce or auto-apply most of the rules so you rarely have to think about them.
Names carry meaning. PEP 8 fixes the casing for each kind of name so that readers can tell what they're looking at from the shape alone.
| Kind of name | Convention | Example |
|---|---|---|
| Variable, function, method | snake_case | cart_total, add_to_cart |
| Class | PascalCase (also called CapWords) | ShoppingCart, OrderStatus |
| Constant | UPPER_SNAKE_CASE | MAX_ITEMS_PER_CART, DEFAULT_TAX_RATE |
| Module, package | short lowercase | cart, orders, email_utils |
| Type variable | short PascalCase | T, KT, ProductT |
| Internal (private) name | leading underscore _name | _normalize_email, _cache |
| Name-mangled (class-private) | double leading underscore __name | __internal_id (used rarely) |
| Dunder (special) | double leading + trailing __name__ | __init__, __str__ |
A small example that puts most of them together:
The naming alone tells you a lot before you read any logic. ShoppingCart is a class. MAX_ITEMS_PER_CART is a constant defined once at module load. add_item is a method you're meant to call. _items has a leading underscore, signaling "this is an implementation detail, don't poke at it from outside the class."
The underscore prefixes have specific meanings that beginners often blur together.
_name) is a convention: "treat this as internal." Python doesn't enforce anything. You can still access cart._items from outside, you just signal that you're crossing a line.__name) triggers name mangling inside a class body. self.__internal_id inside class ShoppingCart becomes self._ShoppingCart__internal_id after compilation. This was designed to avoid accidental clashes in subclasses, not to provide privacy. Most codebases reach for it rarely; a single underscore is enough for almost every case.__name__) are reserved for Python itself. Don't invent your own dunder names; you'll collide with future language features.class_, type_) is the standard workaround when you want a name that's also a Python keyword. class is reserved, so class_ is the conventional escape hatch.PEP 8 explicitly calls out a few characters that look like other characters in some fonts:
l (lowercase L), O (uppercase o), and I (uppercase i) are easy to mistake for 1 and 0. Don't use them as standalone variable names.for i in range(10):) and well-known math conventions.list = [1, 2, 3] shadows the built-in list type for the rest of the scope. Same for type, id, dict, str, filter, map, and so on. If you genuinely need a name like type, add a trailing underscore: type_.Python uses indentation as syntax, so PEP 8's rules here are tighter than in languages where indentation is cosmetic.
Aligned to the opening delimiter:
Hanging indent (the more common modern style, especially with formatters):
The hanging-indent version is what black and ruff format produce, and it's what most modern Python code looks like. The trailing comma after the last argument is intentional: it makes diffs cleaner when you add another argument later, because the existing last line doesn't change.
This is the most-debated rule in PEP 8. The original limit was 79 characters for code and 72 for docstrings and comments. The reasoning was practical: it fits in an 80-column terminal, two files fit side by side on most screens, and email clients wrap long lines.
Modern practice has drifted. The defaults you'll see in the wild:
| Limit | Origin | Where you'll see it |
|---|---|---|
| 79 | Original PEP 8 | Standard library, conservative codebases |
| 88 | black's default | Most modern open-source Python projects |
| 100 | Google's Python style guide, internal use | Many companies, some popular libraries |
| 120 | Some Java-influenced teams | Rare in Python |
PEP 8 itself was updated to acknowledge that "some teams strongly prefer a longer line length" and explicitly permits up to 99 characters when the team agrees. Whatever you pick, pick one number and put it in pyproject.toml so the formatter enforces it.
The 88-character default that black introduced isn't arbitrary. The author's argument was that 10% more horizontal room lets nearly every reasonable function signature fit on one line, which kept code denser without sacrificing readability on a standard laptop screen.
When a line genuinely has to wrap, prefer breaking inside parentheses, brackets, or braces over using a backslash continuation:
PEP 8 also recommends breaking before the operator, not after. The reasoning: when you read the wrapped lines, the operator at the start of each line tells you immediately what's happening.
Cost: A backslash continuation (\ at end of line) is fragile. A single trailing space after the backslash silently breaks the continuation, and most editors don't show trailing whitespace by default. Use parentheses instead.
Imports are the first non-trivial thing a reader sees in a file, so PEP 8 spends a fair amount of time on them.
Imports are grouped into three blocks, separated by a single blank line:
os, sys, json, datetime)requests, pydantic, pytest)Inside each block, imports are sorted alphabetically. Putting it together:
The visual separation makes the file's external surface obvious at a glance: you can see at the top exactly which third-party libraries this module pulls in.
Each import statement should import one top-level name, with one exception.
Correct:
Wrong:
The exception is from module import a, b, c, which is fine because it's still importing from a single module. If the list gets long, wrap it:
PEP 8 recommends absolute imports in nearly all cases. They make the source of every name unambiguous and survive refactors well.
Absolute:
Relative:
The dot syntax in relative imports means "the current package." A single dot is the current package, two dots is the parent, and so on. Relative imports work only inside a package and can be useful when a package is renamed (the imports keep working without changes). But they make a line harder to read in isolation, and they break when someone copies a file out to test it. The general advice: use absolute imports by default, and reach for relative imports only inside a tight package where the alternative is annoyingly long.
The diagram captures the layout most Python files share: three sorted blocks of imports separated by blank lines, with module code following after a final blank line. Tools like isort and ruff enforce this automatically.
A few patterns PEP 8 calls out as bad form:
from module import *) pull every public name into your namespace and make it impossible to tell where a name came from. Avoid them except in carefully controlled cases (some __init__.py files use them with an __all__ list).PEP 8's whitespace rules are mechanical but they're what makes Python files feel "right" to a reader. Tools like black and ruff format handle every rule below for you, but you should still recognize them in code review.
One space on each side of a binary operator, none inside unary operators:
When operators have different priorities, you may add spacing around the lower-priority operators to make the grouping visual:
The exception that surprises beginners: no spaces around `=` in keyword arguments or default values.
Why? Because quantity: int = 1 is a default value, not an assignment, and quantity=2 in a function call is a keyword argument, not an assignment. PEP 8 wants you to spot that difference visually. Add spaces around = only when there's an annotation that doesn't have a default:
One space after, no space before:
Slice colons are special: PEP 8 treats : as a binary operator in slices, so you typically write a[1:5] with no spaces. When slice expressions get more complex, equal spacing on both sides is fine: a[1 : 5 : 2]. Either is acceptable, but pick one per file.
Blank lines separate things at the visual level the same way indentation separates them at the syntactic level:
The two blank lines around class ShoppingCart, def serialize_cart, and def deserialize_cart give the file a clear top-level rhythm. Inside the class, single blank lines between methods keep them visually distinct without making the class sprawl.
No space immediately inside parentheses, brackets, or braces:
This rule has zero exceptions in normal code. If you see leading or trailing whitespace inside a bracket, it's almost always a typo.
PEP 8 has surprisingly little to say about string quotes. Single quotes and double quotes are treated as equivalent; pick one and stay with it inside a file. The pragmatic argument for double quotes: they match what black produces by default and what most JSON and JavaScript code uses, which reduces context-switching cost. Triple-double-quotes ("""...""") are the convention for docstrings, set by PEP 257.
The one case where the choice matters: when the string itself contains a quote, pick the other one to avoid escaping.
PEP 8 has more to say about how comments are written than where they go.
# (hash, then a single space), and the block sits at the same indentation as the code.# , then the comment. Use them sparingly; if you need an inline comment, ask first if a clearer variable name would replace it.PEP 257 is the companion guide for docstring conventions. The highlights you need here:
"""..."""."""Return the cart subtotal.""".The exact format inside the docstring (Google style, NumPy style, reStructuredText) is a project choice, not a PEP 8 rule.
PEP 8 itself names the situations where you should break its rules:
A concrete example: a class that mimics an external API often uses camelCase method names because that's what the wrapped API uses. Forcing snake_case would mean every caller has to mentally translate.
That's a legitimate break. Adding camelCase because you came from a JavaScript background is not.
The summary rule from PEP 8 itself: "Know when to be inconsistent. Sometimes style guide recommendations just aren't applicable. When in doubt, use your best judgment. Look at other examples and decide what looks best."
10 quizzes