PEP 8 is the official style guide for Python code — a set of conventions for formatting and naming that make code consistent and readable across the community. It's a recommendation, not a language rule: code that violates PEP 8 still runs fine, but consistency aids collaboration.
# PEP 8 style
def calculate_total(items, tax_rate=0.0):
subtotal = sum(items)
return subtotal * (1 + tax_rate)
# not PEP 8 — cramped, inconsistent spacing/naming
def calcTotal(items,taxRate=0.0):
subTotal=sum(items);return subTotal*(1+taxRate)
PEP 8 covers indentation (4 spaces), naming, whitespace, imports, and line length. Its guiding principle: readability counts, since code is read far more often than it's written.
PEP 8 assigns a distinct case to each kind of name so readers can tell them apart at
a glance. snake_case for functions, variables, and modules;
PascalCase (CapWords) for classes; UPPER_SNAKE_CASE for constants. A
single leading underscore signals "internal".
MAX_RETRIES = 3 # constant — UPPER_SNAKE_CASE
class HttpClient: # class — PascalCase
def send_request(self): # method — snake_case
retry_count = 0 # variable — snake_case
self._session = None # _leading underscore = "internal"
Avoid single-character names like l, O, I (they look like digits). Method/
function names use the same snake_case as variables; only classes and exceptions
use PascalCase.
Imports go at the top of the file, one per line, grouped in order: standard library, third-party, then local — separated by blank lines. For line length, PEP 8 recommends a maximum of 79 characters (72 for docstrings/ comments), though many modern teams relax this to 88 or 100.
# standard library
import os
import sys
# third-party
import requests
# local
from myapp.utils import helper
# avoid: import os, sys (multiple on one line)
Keeping imports sorted and grouped makes dependencies obvious. The line-length cap
keeps code readable in side-by-side diffs and narrow editors; tools like isort
automate import ordering.
The Zen of Python (PEP 20) is a collection of 19 guiding aphorisms that
capture Python's design philosophy. You can read it any time by running
import this. It informs why the language and its idioms look the way they do.
import this
# Beautiful is better than ugly.
# Explicit is better than implicit.
# Simple is better than complex.
# Readability counts.
# There should be one-- and preferably only one --obvious way to do it.
# ...and 14 more
The aphorisms favor clarity, simplicity, and explicitness over cleverness. They aren't enforced rules but a cultural compass — when two approaches compete, the Zen usually points to the more Pythonic one.
A formatter automatically rewrites your code into a consistent layout; a linter analyzes code and reports style violations and likely bugs without (usually) changing it. black is the dominant formatter (opinionated, near-zero-config); ruff is an extremely fast linter (and formatter) that consolidates many older tools.
# before black
x = {'a':1,'b':2}
# after black
x = {"a": 1, "b": 2}
# command line
# black . -> reformats files in place
# ruff check . -> reports lint issues
# ruff check --fix -> auto-fixes what it can
Running a formatter ends style arguments in code review (the tool decides), while a linter catches unused imports, undefined names, and anti-patterns. Most teams run both, often automatically via pre-commit hooks or CI.
PEP 8 itself says "A Foolish Consistency is the Hobgoblin of Little Minds" — style serves readability, so break the rules when following them would make code less readable or when you must stay consistent with surrounding code or an existing API.
# matching an external library's camelCase API
def setUp(self): # unittest requires this exact name
...
# aligning related assignments can aid readability in some cases
x = 1
longer = 2
Legitimate reasons: compatibility with code that predates PEP 8, conforming to a framework's required names, or when a rule genuinely hurts clarity in context. The rule of thumb: deviate only with a clear readability or compatibility justification, not out of laziness.
Use a single space around binary operators and after commas, but no space
inside brackets, before a call's parenthesis, or around = for keyword
arguments/defaults. Consistent whitespace is a big part of the PEP 8 look.
x = a + b # spaces around operators
f(a, b, c) # space after commas, none before "("
d = {"k": 1} # no space inside braces
def g(x, y=0): ... # no spaces around = for defaults
result = func(value=10) # no spaces around = for kwargs
Rule of thumb: spaces around operators and after commas; no spaces hugging brackets
or around = in argument lists. Let a formatter enforce it.
Two blank lines between top-level functions and classes; one blank line between methods inside a class. Use blank lines sparingly within functions to separate logical sections.
import os
def first(): # 2 blank lines before top-level defs
pass
class C:
def method_a(self): # 1 blank line between methods
pass
def method_b(self):
pass
Rule of thumb: 2 blank lines around top-level defs/classes, 1 between methods — it visually chunks the file into units.
PEP 257 covers docstring conventions: use triple double-quotes, write a
one-line summary as an imperative phrase ending in a period, and for multi-line
docstrings put the closing """ on its own line. They're accessible via __doc__.
def fetch(url):
"""Return the response body for the given URL.""" # one-liner
def process(data):
"""Transform and validate the input data.
Longer explanation of behavior, args, and return value.
"""
...
Rule of thumb: every public module/class/function gets a docstring; first line is a concise imperative summary ("Return…", "Compute…"), not "This function…".
Compare to None with is/is not, never ==. Don't compare booleans with
== — test truthiness directly. And use if x is not None, not if not x is None,
for readability.
if x is None: ... # good
if x == None: ... # avoid
if flag: ... # good
if flag == True: ... # avoid
if x is not None: ... # good (not: "if not x is None")
Rule of thumb: is/is not for None; direct truthiness for booleans — explicit
== True/== None is noisy and occasionally wrong.
Prefer implicit continuation inside parentheses/brackets/braces over backslashes. Align wrapped elements or use a hanging indent, and put binary operators before the operand on the next line (PEP 8's updated guidance).
# preferred: implicit continuation
total = (first_value
+ second_value
- third_value)
result = some_function(
arg_one,
arg_two,
)
# avoid backslashes:
total = first_value + \
second_value
Rule of thumb: wrap inside brackets/parens (no backslashes); break before operators and use a trailing comma so diffs stay clean.
Put a space after the colon (not before) in variable/parameter annotations, and
spaces around -> for return types. With an annotation, also put spaces around
= for defaults (unlike unannotated defaults).
def f(x: int, y: str = "a") -> bool: # space after :, around ->, around =
count: int = 0 # annotated variable
return True
def g(x, y="a"): # no annotation -> no spaces around =
...
Rule of thumb: name: type, -> ret, and param: type = default (spaces around =
only when the parameter is annotated).
_name = "internal, by convention" (not enforced). __name (leading only)
triggers name mangling to avoid subclass clashes. __name__ (dunder) is
reserved for Python's own special names — don't invent your own.
class C:
def __init__(self):
self.public = 1 # public API
self._internal = 2 # "don't touch" hint
self.__mangled = 3 # -> self._C__mangled
# __dunder__ names like __init__, __repr__ are Python's — never make new ones
Rule of thumb: _x for internal, __x only when you need mangling, and never create
your own __dunder__ names — they're reserved.
No trailing whitespace, files should end with a single newline, and use
spaces, never tabs for indentation (4 per level). Mixing tabs and spaces is a
TabError in Python 3.
def f():
return 1 # 4 spaces, no trailing space after "1"
# file ends with exactly one newline here
Rule of thumb: 4-space indent, no tabs, no trailing whitespace, newline at EOF — all auto-handled by formatters and editor "trim on save" settings.
PEP 8 takes no position on single vs double quotes — just be consistent. Pick one for normal strings and use the other to avoid escaping. Tools like black standardize on double quotes.
name = "Ada" # black normalizes to double quotes
msg = 'He said "hi"' # use single to avoid escaping the inner "
sql = "SELECT 'x'" # use double to avoid escaping the inner '
Rule of thumb: consistency over preference — adopt a formatter's choice (usually double quotes) and switch quote style only to avoid escapes.
More Pythonic Idioms interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.