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
- Distinctive: line 222
All public modules, functions, classes, methods.
- Very specific: line 336
Don't accumulate via += in a loop — O(n²).
≈ “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.
Files
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):
- Standard library.
- Third-party packages.
- 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, notimport os, sys. - Absolute imports preferred:
from myapp.utils import x, notfrom .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
| Entity | Style | Example |
| Module / package | snake_case | data_processor.py |
| Class | PascalCase | UserAccount, HTTPServer |
| Exception | PascalCase + Error | ValidationError |
| Function, method | snake_case | calculate_total |
| Variable | snake_case | user_count |
| Constant | UPPER_CASE | MAX_CONNECTIONS = 100 |
| Type variable | short PascalCase | T, KT, VT |
Special notes:
selffor instance methods,clsfor classmethod. Notthis, notobj.- 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 self4. 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 # inconsistentKeyword 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 offsetDocstrings (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 ambiguouslyBoolean — without explicit comparison
# Good
if greeting:
...
# Bad
if greeting == True: ...
if greeting is True: ... # has cases, but usually redundantEmpty 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
passexcept 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 intentionalStrings — 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 += pFunctions instead of lambda in def
# Good
def square(x):
return x * x
# Bad
square = lambda x: x * xContext 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 ofList[int](Python 3.9+).int | Noneinstead ofOptional[int](Python 3.10+).- f-strings instead of
%and.format(). match/caseinstead of longif/elifby type.pathlib.Pathinstead ofos.path.join.
Automation
Manual PEP 8 → 0. Use:
ruff check . # linting (replaces flake8/pylint/isort/...)
ruff format . # formatting (replaces black)
mypy . # typesruff 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)In this repository34 other files it gives its agents.
- ckm:banner-designSkill
- ckm:brandSkill
- claude-apiSkill
- data-analyzerSkill
- design-apiSkill
- ckm:designSkill
- ckm:design-systemSkill
- diagnoseSkill
- drilldown-analyzerSkill
- grill-docsSkill
- grill-meSkill
- improve-archSkill
- mcp-builderSkill
- pdfSkill
- playwright-interactiveSkill
- playwrightSkill
- python-codeSkill
- python-fpSkill
- qaSkill
- refactorSkill
- review-securitySkill
- setup-pre-commitSkill
- ckm:slidesSkill
- tddSkill
- to-issuesSkill
- to-prdSkill
- triageSkill
- ubiquitous-languageSkill
- ckm:ui-stylingSkill
- ui-ux-pro-maxSkill
- webapp-testingSkill
- write-docsSkill
- write-skillSkill
- xlsxSkill
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
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.

