Skip to content
  • PEP 8, Linters & Formatters · Python

PEP 8, Linters & Formatters

LearnViaNotes @learnvianotes· 4 min read

Consistent style makes code easier to read and review. PEP 8 defines the conventions; formatters and linters apply and enforce them automatically so humans can review logic instead of whitespace.

PEP 8 essentials

TopicConvention
Indentation4 spaces, never tabs
Line length79 in PEP 8; most teams use 88 (Black/Ruff) or 100
Functions, variables, modulessnake_case
Classes, exceptions, type variablesPascalCase (HttpError, UserProfile)
ConstantsUPPER_SNAKE_CASE at module level
Internal names_leading_underscore
Blank lines2 around top-level functions/classes, 1 between methods
ImportsOne per line, at the top: standard library → third-party → local, each group separated
Whitespacex = f(a, b), items[1:2], no spaces inside brackets, def f(x=1) without spaces around =
Comparisonsis None, if not items:, isinstance(x, int) instead of type(x) == int
Python
import os                                  # 1. standard library
from collections import Counter

# import requests                          # 2. third-party
# from .models import User                 # 3. local package

MAX_RETRIES = 3


class RetryPolicy:
    """Decide whether a failed request should be retried."""

    def __init__(self, max_retries: int = MAX_RETRIES) -> None:
        self.max_retries = max_retries

    def should_retry(self, attempt: int, status: int) -> bool:
        return attempt < self.max_retries and status >= 500


policy = RetryPolicy()
print(policy.should_retry(1, 503), policy.should_retry(3, 503))   # True False

Docstrings (PEP 257)

Python
def transfer(src: str, dst: str, amount: float) -> str:
    """Move money between two accounts.

    Args:
        src: Account id to debit.
        dst: Account id to credit.
        amount: Positive amount in rupees.

    Returns:
        The transaction id.

    Raises:
        ValueError: If amount is not positive.
    """
    if amount <= 0:
        raise ValueError("amount must be positive")
    return f"txn:{src}->{dst}:{amount}"

print(transfer.__doc__.splitlines()[0])      # Move money between two accounts.
print(transfer("a1", "b2", 50))              # txn:a1->b2:50

The tools

ToolRoleNotes
RuffLinter + formatterWritten in Rust, very fast; replaces Flake8, isort, pyupgrade and most plugins
BlackFormatter“Uncompromising” — one style, no debates; Ruff's formatter is compatible
isortImport sorterBuilt into Ruff (I rules)
Flake8 / PylintLintersOlder; Pylint is slower but checks more design issues
mypy / pyrightType checkersCatch type errors statically — see Type Hints & mypy
pre-commitGit hook runnerRuns the tools on every commit
BanditSecurity linterFlags eval, shell=True, weak hashes…
Terminal
$ pip install ruff
$ ruff check .              # lint
$ ruff check . --fix        # auto-fix what's safe (unused imports, sorting…)
$ ruff format .             # format (Black-compatible)
ruff check output
app.py:1:8: F401 [*] `os` imported but unused
app.py:14:5: E722 Do not use bare `except`
app.py:20:15: B006 Do not use mutable data structures for argument defaults
Found 3 errors.
[*] 1 fixable with the `--fix` option.

Configuration in pyproject.toml

pyproject.toml
[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "SIM"]   # pycodestyle, pyflakes, isort, bugbear, pyupgrade, simplify

[tool.mypy]
strict = true
pre-commit
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.9
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

$ pip install pre-commit && pre-commit install   # now runs on every git commit

The Zen of Python

Python
import codecs, contextlib, io

with contextlib.redirect_stdout(io.StringIO()):
    import this                           # "import this" prints the poem on first import
zen = codecs.decode(this.s, "rot13").splitlines()
print(zen[2])                             # Beautiful is better than ugly.
print(zen[3])                             # Explicit is better than implicit.
print(zen[8])                             # Readability counts.

Code-review checklist

  • Names say what things are; no single letters except loop indices and math.

  • Functions do one thing, are short, and return early instead of nesting deeply.

  • No bare except:, no mutable default arguments, no unused imports or variables.

  • Public functions have type hints and a docstring.

  • Resources are opened with with; no secrets in code or logs.

  • New behaviour has tests; the formatter, linter and type checker pass in CI.

What is PEP 8?

Linter vs formatter?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report