PEP 8 is Python’s official style guide — a short document that says how Python code should look. Ruff enforces most of it automatically, so you’ll rarely have to think about the details. But knowing the principles helps when you write something new.
PEP stands for Python Enhancement Proposal — the way new features and conventions get added to the language. PEP 8 happens to be the eighth such proposal and the most widely followed.
Indentation
- 4 spaces per level. Never tabs. Never 2 spaces. Never 8.
- Continuation lines align with the opening delimiter or use a hanging indent.
# good — 4-space indent
def my_function(arg1, arg2):
result = arg1 + arg2
return result
# good — aligned with opening delimiter
result = some_function(arg_one,
arg_two,
arg_three)
# good — hanging indent
result = some_function(
arg_one,
arg_two,
arg_three,
)
Line length
PEP 8 says 79 characters max. Most modern projects use 88 or 100. Pick one for your project and let Ruff enforce it.
Blank lines
- Two blank lines between top-level functions and classes.
- One blank line between methods inside a class.
- Use blank lines inside functions sparingly, to separate logical sections.
def first_function() -> None:
...
def second_function() -> None: # two blank lines before
...
class MyClass:
def method_one(self) -> None:
...
def method_two(self) -> None: # one blank line before
...
Imports
- One import per line.
- Imports go at the top of the file, after the module docstring.
- Grouped in three blocks: standard library, third-party, your own code.
- Blank line between groups.
# standard library
import json
import sys
from pathlib import Path
# third-party
import numpy as np
import pandas as pd
# your own code
from my_project import config
from my_project.models import User
Ruff (with the I rules turned on) sorts all of this automatically.
Naming
snake_case— variables, functions, modulesALL_CAPS— constantsPascalCase— classes_single_leading_underscore— internal use only (“don’t touch”)__double_leading_underscore— name mangling (rarely used)single_trailing_underscore_— when you need to avoid a keyword (class_,type_)
MAX_RETRIES = 5
def parse_user_input(text: str) -> dict[str, str]:
...
class UserProfile:
def __init__(self) -> None:
self.name: str = ""
self._cache: dict[str, str] = {}
Whitespace
- One space around binary operators:
a + b,x = 5. - No space before parentheses or brackets when calling:
func(x),arr[0]. - No space inside parentheses or brackets:
(x, y),[1, 2, 3]. - One space after commas:
[1, 2, 3]. - No spaces around
=in default arguments:def f(x=1):.
# good
x = a + b
result = func(arg1, arg2)
nums = [1, 2, 3]
# bad
x=a+b
result = func( arg1,arg2 )
nums = [ 1,2,3 ]
Ruff handles all of this for you. Don’t memorise the rules — just save the file.
Comments
- Sentences start with a capital letter.
- End with a period.
- Avoid stating what the code already says.
- Explain why, not what.
# bad — restates the code
x = x + 1 # increment x
# good — explains the reason
x = x + 1 # account for off-by-one in legacy API
The best comment is no comment, paired with clearer code:
# OK
i = i + 1 # next index
# better
next_index = i + 1
Docstrings
Use triple-quoted strings on the first line of a module, function, class, or method:
def parse(text: str) -> int:
"""Return text parsed as an integer."""
return int(text)
For longer docstrings, the first line is a summary; the rest is detail (we covered this in Section 5).
Function design
- Functions should do one thing.
- Functions should be small — if it doesn’t fit on a screen, consider splitting.
- Avoid mutable defaults (use
Noneand create the real default inside). - Prefer pure functions — no side effects when avoidable.
When to break the rules
PEP 8 itself says: “A foolish consistency is the hobgoblin of little minds.” If a rule makes code harder to read in a specific case, break it. The point of style is communication, not obedience.
But — Ruff format and the major rules are universally followed. Don’t fight them; let them happen automatically and put your energy into the parts that matter — naming, decomposition, design.
Summary of Section 15
You can now:
- Read a traceback and understand exactly what went wrong
- Pause a running program with
breakpoint()and step through withpdb - Use the
loggingmodule for proper structured output - Run Ruff to format and lint your code
- Recognise PEP 8 conventions
These skills compound. The earlier you build them, the less time you spend stuck — and the easier your code is for other people to read.
What’s next
Final section. Section 16: NumPy Fundamentals — your first taste of array programming and the bridge to machine learning.