PEP 8 & Code Style
PEP 8 is Python's official style guide — a shared set of conventions for naming, layout, and whitespace that makes any Python codebase readable to any Python developer.
PEP 8 ("Style Guide for Python Code") is the standard-library-adjacent style guide written by Guido van Rossum, Barry Warsaw, and Nick Coghlan and maintained as part of the Python Enhancement Proposal process. It doesn't change what your code does — it standardizes how it looks, so that any two Python developers reading each other's code encounter the same conventions instead of reinventing formatting choices project by project. Consistency, not personal taste, is the point: PEP 8 itself says "a style guide is about consistency," and that consistency with the surrounding project should usually win over strict adherence to the letter of the guide.
What Is PEP 8 and Why It Matters
PEP 8 is a document, not a tool — it describes conventions in prose. It covers naming, indentation, line length, whitespace, import ordering, comments, and more. It is descriptive of good practice across the Python ecosystem rather than enforced by the interpreter: Python will happily run code that violates every rule in PEP 8, because these are human-readability conventions, not syntax rules.
Code is read far more often than it is written — by teammates, by code reviewers, by your future self six months later. Shared conventions remove an entire category of friction: you stop noticing formatting because it's uniform, and your attention goes to logic instead. This matters more as a codebase or team grows; a one-person script can get away with anything, but a shared codebase cannot.
Naming Conventions
PEP 8 prescribes different naming cases for different kinds of names, so that a name's shape hints at what it is before you even look up its definition.
A single leading underscore is a convention ("internal, not part of the public API") that Python does not enforce. A double leading underscore triggers name mangling inside a class, which is a real language mechanism — see the related Encapsulation / Name Mangling topic.
Avoid single-character names l, O, and I — in many fonts they're visually indistinguishable from 1 and 0.
# functions and variables: snake_case
def calculate_total(order_items):
unit_price = 9.99
return unit_price * len(order_items)
# classes: CapWords (PascalCase)
class ShoppingCart:
pass
# constants: UPPER_SNAKE_CASE, module level
MAX_RETRIES = 3
DEFAULT_TIMEOUT_SECONDS = 30.0
# "private" (convention only, not enforced) with a leading underscore
class Connection:
def __init__(self):
self._socket = None # internal use
self.__buffer = bytearray() # name-mangled, stronger "keep out" signal
# modules and packages: short, lowercase, no underscores if possible
# e.g. json, itertools, urllib.parseIndentation
PEP 8 mandates 4 spaces per indentation level, and spaces over tabs. Python 3 disallows mixing tabs and spaces in a way that makes indentation ambiguous — the interpreter raises TabError rather than guessing, because silently misinterpreting indentation is exactly the kind of bug that's invisible in an editor but changes program behavior.
def process(items):
total = 0
for item in items:
if item.is_valid():
total += item.value
return total
# continuation lines: align with the opening delimiter, or use a
# hanging indent with nothing on the first line
result = some_function(
argument_one,
argument_two,
argument_three,
)Line Length
PEP 8 recommends limiting lines to 79 characters (72 for docstrings/comments), a legacy of fitting two files side by side on old terminals. In practice, most modern teams and tools relax this to 88 or 100 characters — the widely used Black formatter defaults to 88 — while still keeping *some* limit, because very long lines are genuinely harder to scan and to diff. What matters is picking one limit per project and enforcing it consistently, not the exact number.
# hard to scan
result = some_object.method_one().method_two().method_three().method_four()
# wrapped with parentheses (preferred over a trailing backslash)
result = (
some_object
.method_one()
.method_two()
.method_three()
.method_four()
)Import Style
PEP 8 groups imports into three blocks, each separated by a blank line and each typically sorted alphabetically: (1) standard-library imports, (2) third-party imports, (3) local/first-party imports. One import per line for import statements; from module import a, b, c may combine names from one module on one line.
Avoid from module import * in real code: it dumps every public name from a module into your namespace, silently shadowing existing names and making it impossible for a reader (or a linter) to tell where a given name came from.
# standard library
import json
import os
from pathlib import Path
# third-party
import requests
from sqlalchemy import create_engine
# local application
from myapp.models import User
from myapp.utils import slugifyWhitespace Rules
PEP 8 has specific rules for whitespace around operators, after commas, and around function calls/indexing — the goal is one consistent visual rhythm so the eye doesn't have to adjust file to file.
One notable exception: no spaces around = for keyword arguments or default parameter values that lack a type annotation, but spaces are added when a type annotation is present.
# good
x = 1
y = x + 1
spam(ham[1], {eggs: 2})
def greet(name: str, *, loud: bool = False) -> str:
...
# PEP 8 violations
x=1 # missing spaces around =
y = x+1 # missing spaces around +
spam( ham[ 1 ], { eggs: 2 } ) # extra spaces inside brackets
def greet (name): # space before the opening paren of a call/def
...def connect(timeout=30): # no spaces: default value, no annotation
...
def connect(timeout: int = 30): # spaces: annotation present
...
connect(timeout=10) # keyword argument at a call site: no spacesReadability Principles
Beyond the mechanical rules, PEP 8 encodes broader readability judgment: prefer explicit comparisons (if x is None over if x == None), avoid compound statements on one line, use is/is not for None/singleton checks, and prefer positive conditions over double negatives. These overlap with the Zen of Python (import this), which PEP 8 explicitly cites as a guiding philosophy — "readability counts" and "explicit is better than implicit" are the values these mechanical rules are trying to serve.
# discouraged
if not (x is None):
...
if x == None:
...
if x != None: y = 1; z = 2
# preferred
if x is not None:
...
if x is None:
...
if x is not None:
y = 1
z = 2Automated Formatters
Manually policing every PEP 8 rule in code review doesn't scale, and it's tedious for both the author and the reviewer. Automated formatters (third-party tools such as Black and Ruff's formatter) rewrite your source code to a canonical, PEP-8-compatible style automatically, so the question "is this line spaced correctly?" stops being a human decision at all. This is covered in depth in the dedicated Formatting & Linting topic; the point to internalize here is that PEP 8 defines the target style, and formatters are how real teams reach it without manual effort.
pycodestyle (formerly pep8, third-party) is a linter that specifically checks PEP 8 compliance and reports violations without rewriting your code; formatters like Black go further and *rewrite* the file to comply.
Complete Example
Snake_case functions/variables, CapWords class, UPPER_SNAKE_CASE constant, consistent whitespace, grouped imports, and a docstring — every PEP 8 rule covered above applied together.
"""Order total calculation utilities."""
from __future__ import annotations
from dataclasses import dataclass
TAX_RATE = 0.08
@dataclass
class LineItem:
name: str
unit_price: float
quantity: int = 1
@property
def subtotal(self) -> float:
return self.unit_price * self.quantity
def calculate_total(items: list[LineItem], *, apply_tax: bool = True) -> float:
"""Return the total price for a list of line items."""
subtotal = sum(item.subtotal for item in items)
if apply_tax:
return subtotal * (1 + TAX_RATE)
return subtotalQuick Reference
Element Convention
-------------------- --------------------------------
functions/variables snake_case
classes CapWords (PascalCase)
constants UPPER_SNAKE_CASE
"internal" attribute _leading_underscore (convention)
"mangled" attribute __leading_double_underscore
indentation 4 spaces, never tabs
line length 79 chars (PEP 8) / 88 (common in practice, e.g. Black)
imports stdlib / third-party / local, each group sorted
blank lines 2 around top-level defs, 1 around methods
comparisons to None is / is not, never == / !=Key Takeaways
PEP 8 is a style guide, not enforced by the interpreter — code that violates it still runs.
Its core value is consistency: within one project, following the project's established style beats blind adherence to the letter of PEP 8.
Naming case signals kind: snake_case for functions/variables, CapWords for classes, UPPER_SNAKE_CASE for constants.
Use 4-space indentation, never mixed tabs and spaces; pick one line-length limit and keep it consistent.
Automated formatters (Black, Ruff) apply most PEP 8 rules mechanically, removing the need to police whitespace by hand.
if x == None: handle_missing() if x is None: handle_missing()
None is a singleton; identity comparison with is is both the PEP 8 convention and avoids relying on a custom __eq__ that might behave unexpectedly against None.
from math import * def area(r): return pi * r ** 2 import math def area(r: float) -> float: return math.pi * r ** 2
Wildcard imports hide where names come from and can silently shadow existing names; an explicit module import keeps the source of every name traceable.
def f(a,b,c): return a+b+c def f(a, b, c): return a + b + c Missing spaces after commas and around operators are pure PEP 8 whitespace violations that hurt scanability without changing behavior.
class shopping_cart: def AddItem(self, Item): ...
class ShoppingCart: def add_item(self, item): ...
Mixing naming conventions (a lowercase class, a CapWords method) breaks the visual cue that lets readers instantly recognize what kind of name they're looking at.
Why is is None preferred over == None under PEP 8?
None is a singleton object; is checks identity directly and cannot be fooled by a class that overrides __eq__ to behave unexpectedly when compared to None. It's also faster since it skips the equality machinery entirely.
What naming convention does PEP 8 prescribe for a class named HttpClient and a function that creates one?
The class uses CapWords: HttpClient. The function uses snake_case: create_http_client or similar — case signals whether a name is a class or a callable/variable.
Does PEP 8 forbid running code that violates its rules?
No. PEP 8 is a style guide, not enforced by the Python interpreter. Code with inconsistent spacing, wrong-case names, or 300-character lines still executes; PEP 8 violations are caught by linters/formatters, not by python itself.
Why does PEP 8 recommend grouping imports into stdlib / third-party / local blocks?
It gives a reader an immediate sense of a module's dependency footprint — what's built in, what's an external dependency, and what's part of the same codebase — without reading each import individually.
What's the PEP 8-preferred way to break a long function call across multiple lines?
Wrap the arguments in the call's own parentheses with a hanging indent (one argument per line, trailing comma), rather than using a backslash continuation, which is fragile and harder to read.
Why might a real project choose an 88-character line limit instead of PEP 8's 79?
PEP 8's 79-character limit dates from narrower terminal-era displays; many modern teams (e.g. those using Black, which defaults to 88) find a slightly longer limit reduces awkward wrapping on modern screens while still capping line length for diffability.
Rewrite this line to follow PEP 8 whitespace and naming rules: def Calc(x,y): return x*y+1 def calc(x: int, y: int) -> int: return x * y + 1
Function name changed to snake_case, added spaces around operators and after the comma, and the body moved to its own indented line rather than being a one-line compound statement.
This code runs and prints "empty" correctly. What PEP 8 issue does it still have, and why does it matter even though the output is correct?
It uses == None instead of is None. It happens to work here because None's __eq__ behaves normally, but it's not guaranteed for arbitrary objects with custom __eq__ methods, and it's slower and non-idiomatic — PEP 8 recommends is/is not for singleton comparisons regardless of whether == would also work.
Write a short module-level docstring-free snippet defining a constant MAX_CONNECTIONS and a function open_connection that uses it, following PEP 8 naming and whitespace conventions.
MAX_CONNECTIONS = 10 def open_connection(pool_size: int = MAX_CONNECTIONS) -> None: print(f"opening pool of size {pool_size}")
MAX_CONNECTIONS is UPPER_SNAKE_CASE (module-level constant), open_connection is snake_case, two blank lines separate the top-level constant from the function, and the annotated default uses spaces around =.
x = None
if x == None:
print("empty")
else:
print("has value")