agentleFS
wachawowachawo/claude-skills/skills/python-pep8/SKILL.md

python-pep8

Skill0 starsChanged 6 months agoMITView on GitHub

Detailed PEP 8 guide for Python code — layout, imports, names, whitespace, comments, programming recommendations (None comparisons, exceptions, return, strings). Use when reviewing or fixing the style of a Python file, mentioning "PEP 8", "code style", or wanting to bring code into line with the canon.

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    All public modules, functions, classes, methods.

    line 222
  2. Very specific:

    Don't accumulate via += in a loop — O(n²).

    line 336

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

PEP 8 for Python

A summary of PEP 8 with practical do/don't examples. Ruff/black handle formatting automatically — this guide is about the semantic decisions auto-formatters do not make.

1. Code layout

Indentation

4 spaces per level. No tabs in new code.

# OK
def f(
        a, b, c,
        d):
    print(a)

# OK (vertical alignment)
result = some_fn(arg1, arg2,
                 arg3, arg4)

Line length

  • Code: 79 characters (PEP 8) or 88 (black/ruff default — recommended).
  • Docstrings/comments: 72.
# Bad (too long)
result = some_very_long_function_name_that_exceeds_the_character_limit(a, b, c)

# Good
result = some_very_long_function_name_that_exceeds_the_character_limit(
    a, b, c,
)

Blank lines

  • Two blank lines between top-level functions/classes.
  • One between methods of a class.
  • Blank lines inside a function — sparingly, for logical blocks.
class A:
    def m1(self): ...

    def m2(self): ...


def standalone():
    ...

Line breaks at binary operators

Break before the operator, not after (mathematical convention, reads more clearly).

# Good
income = (gross_wages
          + taxable_interest
          - ira_deduction)

# Bad
income = (gross_wages +
          taxable_interest -
          ira_deduction)

Encoding

UTF-8 without BOM. No # -*- coding: utf-8 -*- in Python 3 (it is already the default).

2. Imports

Groups (with a blank line between):

  1. Standard library.
  2. Third-party packages.
  3. Local modules.
import os
import sys
from datetime import datetime

import flask
import peewee

from models import User
from serializers import user_serializer
  • One import per line: import os + import sys, not import os, sys.
  • Absolute imports preferred: from myapp.utils import x, not from .utils import x (except in explicit package cases).
  • No from x import *.
  • Imports at the top of the file, after the module docstring.

3. Naming

EntityStyleExample
Module / packagesnake_casedata_processor.py
ClassPascalCaseUserAccount, HTTPServer
ExceptionPascalCase + ErrorValidationError
Function, methodsnake_casecalculate_total
Variablesnake_caseuser_count
ConstantUPPER_CASEMAX_CONNECTIONS = 100
Type variableshort PascalCaseT, KT, VT

Special notes:

  • self for instance methods, cls for classmethod. Not this, not obj.
  • Conflict with a keyword: class_, type_ (trailing underscore).
  • One leading _ — non-public (by convention, not enforced).
  • Two leading __ — name mangling (only meaningful in inheritance).
  • Two on both sides __name__ — dunder, do not define your own.
# Bad
class user_account: pass         # should be PascalCase
def CalcTotal(): pass            # should be snake_case
maxConnections = 100             # should be UPPER_CASE
def m(this, x): pass             # should be self

4. Whitespace in expressions

Don't crowd

# Good
spam(ham[1], {eggs: 2})
if x == 4:
    print(x)

# Bad
spam( ham[ 1 ], { eggs: 2 } )
if x == 4 :
    ...

Binary operators

One space around. With mixed precedence, you may omit spaces around higher-precedence operators.

# Good
i = i + 1
hypot2 = x*x + y*y    # acceptable: multiplication has higher precedence

# Bad
i=i+1
x = x*2-1             # inconsistent

Keyword arguments / default values

No spaces around = for default parameter values and kwargs.

# Good
def fn(real, imag=0.0):
    return magic(r=real, i=imag)

# Bad
def fn(real, imag = 0.0):
    return magic(r = real, i = imag)

But when there is a type annotation, spaces around = are present:

# Good
def fn(real: float, imag: float = 0.0): ...

# Bad
def fn(real: float, imag: float=0.0): ...

Commas, colons, semicolons

Space after, not before.

# Good
if x == 4: print(x, y)
foo = (0,)

# Bad
if x == 4 : print(x , y)
foo = (0, )

5. Comments and docstrings

Block comments

# + one space. Full sentences, capital letter. Indentation level matches the code being commented on.

# Apply tax at the regional rate.
# Round to two decimal places — compliance requirement.
total = base * (1 + tax_rate)

Inline comments

At least 2 spaces before #, then # . Use sparingly — only when explaining why, not what.

x = x + 1  # Compensate for boundary offset

Docstrings (PEP 257)

  • All public modules, functions, classes, methods.
  • Single-line — """...""", no blank lines.
  • Multi-line — closing """ on its own line.
def fetch_data():
    """Return processed data."""

def long_one():
    """Short summary.

    Longer description with details and examples.
    """

6. Programming recommendations

Comparing with None — is / is not

# Good
if x is None: ...
if x is not None: ...

# Bad
if x == None: ...
if not x is None: ...     # reads ambiguously

Boolean — without explicit comparison

# Good
if greeting:
    ...

# Bad
if greeting == True: ...
if greeting is True:  ...   # has cases, but usually redundant

Empty sequences — truthiness, not len()

# Good
if not items: ...
if items:     ...

# Bad
if len(items) == 0: ...
if len(items) > 0:  ...

Exceptions — specific, not bare except

# Good
try:
    import platform
except ImportError:
    platform = None

# Bad
try:
    ...
except:               # also catches KeyboardInterrupt, SystemExit
    pass

except Exception: is acceptable when you really do catch everything and log it. except: — almost never.

return — consistent style

Either every branch returns a value, or none do. Explicit return None.

# Good
def f(x):
    if x >= 0:
        return math.sqrt(x)
    return None

# Bad
def f(x):
    if x >= 0:
        return math.sqrt(x)
    # implicit return None — unclear to the reader whether intentional

Strings — startswith/endswith instead of slices

# Good
if filename.endswith(".txt"): ...
if name.startswith("test_"):  ...

# Bad
if filename[-4:] == ".txt": ...

Type — isinstance, not type()

# Good
if isinstance(x, int): ...
if isinstance(x, (int, float)): ...

# Bad
if type(x) is int: ...
if type(x) == int: ...

String concatenation

Don't accumulate via += in a loop — O(n²). "".join(...) is linear.

# Good
result = "".join(parts)

# Bad
result = ""
for p in parts:
    result += p

Functions instead of lambda in def

# Good
def square(x):
    return x * x

# Bad
square = lambda x: x * x

Context managers for resources

# Good
with open(path) as f:
    data = f.read()

# Bad
f = open(path)
try:
    data = f.read()
finally:
    f.close()

7. Modern additions (Python 3.9+)

PEP 8 is the baseline. Modern rules ruff enables by default:

  • list[int] instead of List[int] (Python 3.9+).
  • int | None instead of Optional[int] (Python 3.10+).
  • f-strings instead of % and .format().
  • match/case instead of long if/elif by type.
  • pathlib.Path instead of os.path.join.

Automation

Manual PEP 8 → 0. Use:

ruff check .       # linting (replaces flake8/pylint/isort/...)
ruff format .      # formatting (replaces black)
mypy .             # types

ruff is the only linter you need in 2025+. Configure in pyproject.toml:

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

[tool.ruff.lint]
select = [
    "E", "W",   # pycodestyle (PEP 8)
    "F",        # pyflakes
    "I",        # isort
    "B",        # bugbear
    "UP",       # pyupgrade
    "SIM",      # simplify
    "C4",       # comprehensions
    "N",        # pep8-naming
]

Checklist

Layout:
- [ ] 4-space indent, no tabs
- [ ] Lines ≤ 88 characters (docstrings ≤ 72)
- [ ] 2 blank lines between top-level def/class
- [ ] Line break BEFORE the binary operator

Imports:
- [ ] Groups: stdlib / third-party / local
- [ ] One import per line
- [ ] No `from x import *`

Names:
- [ ] Class — PascalCase
- [ ] Function/variable — snake_case
- [ ] Constant — UPPER_CASE

Programming:
- [ ] `is None` / `is not None` (not `== None`)
- [ ] Specific `except`, not bare
- [ ] `if not items` (not `len(items) == 0`)
- [ ] `isinstance(x, T)` (not `type(x) is T`)
- [ ] `"".join(parts)` for concatenation in a loop
- [ ] `with open(...)` for resources
- [ ] `def`, not `name = lambda`

Tooling:
- [ ] `ruff check .` passes
- [ ] `ruff format .` applied
- [ ] `mypy .` passes (in strict, if possible)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.