Code Style and Good Habits
Writing code others can read: PEP 8, naming, comments and docstrings, and the basics of type hints.
Why style matters
Code is read far more often than it is written: by colleagues, by
reviewers, and by yourself six months later. Python does not care
whether a variable is called x or
monthly_total, but a reader does. Consistent style
removes small distractions, so the reader's attention goes to what
the code does rather than to how it is laid out.
Style is also a team agreement. When everyone follows the same rules, code written by different people looks the same, and changes show up in version control as real changes instead of reshuffled spaces.
PEP 8: the style guide
PEP 8 is the official style guide for Python code, titled "Style Guide for Python Code". A PEP (Python Enhancement Proposal) is a design document of the Python project; PEP 8 is the one almost every Python project follows. Its most important rules follow below.
Indentation and line length
- Indent with 4 spaces per level: spaces, not tabs. Every editor can insert 4 spaces when you press Tab.
- Keep lines to at most 79 characters (72 for comments and docstrings). Many teams agree on a slightly longer limit, such as 88; PEP 8 allows that, as long as it is consistent.
-
Break long lines inside parentheses, brackets or braces, where
Python continues the line automatically. Avoid the backslash
(
\) for this.
base_price = 120.0
shipping = 4.95
discount = 15.0
total_price = (
base_price
+ shipping
- discount
)
print(total_price)
109.95
Blank lines
Use two blank lines around top-level functions and classes, and one blank line between methods inside a class. Inside a function, a single blank line can separate logical steps, like paragraphs in a text.
Whitespace
Put one space around assignment and comparison operators and after
commas, and none just inside brackets. An exception: no spaces
around = for keyword arguments and default values —
except when the parameter has a type hint:
def area(width: float = 1.0).
# Good
total = price * quantity + shipping
items = ["pen", "paper", "stapler"]
print(total, end="\n")
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Avoid
total=price*quantity+shipping
items = [ "pen" ,"paper" , "stapler" ]
print(total, end = "\n")
Imports
Put imports at the top of the file, one module per
import line, in three groups separated by a blank line:
the standard library, then third-party packages, then your own
project's modules. Tools such as isort or Ruff also sort each group
alphabetically.
import json
import sys
from pathlib import Path
import requests
from shop import billing
How imports work, and why from module import * is best
avoided, is covered in
Modules, Packages, pip and Virtual Environments.
Naming conventions
PEP 8 uses the shape of a name to tell you what kind of thing it is:
-
snake_case— lowercase words joined by underscores — for variables, functions, methods and modules:order_total,send_invoice(),price_list.py. -
PascalCase(also called CapWords) for classes:BankAccount,TemperatureSensor. -
UPPER_CASEfor constants — values set once at module level and never changed:MAX_RETRIES,VAT_RATE. Python does not enforce this; the name tells readers not to change it. -
A leading underscore,
_cache, marks a name as internal to its module or class. See encapsulation.
Good names describe what a value means, not its type:
unpaid_invoices says more than list2.
Single letters are fine only for short loops and well-known math
(i, x, y). Avoid the letters
l, O and I as names: in many
fonts they look like the digits 1 and 0.
Comments and docstrings
Comments
A comment (after #) is for readers of the code. The
best comments explain why something is done, because the
code already shows what is done. Write them as full
sentences, and keep them up to date: a wrong comment is worse than
none.
retries = 0
# Bad: repeats the code
retries += 1 # add 1 to retries
# Good: explains the reason
retries += 1 # the payment API sometimes times out on the first call
Docstrings
A docstring is a string literal
placed as the first statement of a module, function, class or
method. Unlike a comment, Python keeps it: it becomes the object's
__doc__ attribute and is what
help() shows. The conventions are described in
PEP 257: use triple double quotes, start with a
one-line summary written as a command ("Return …", not "Returns …"),
and add details after a blank line when needed.
def net_price(gross, vat_rate=0.21):
"""Return the price without VAT, rounded to cents."""
return round(gross / (1 + vat_rate), 2)
def shipping_cost(weight_kg, express=False):
"""Calculate the shipping cost of a parcel in euros.
Parcels up to 2 kg cost a flat 4.95; heavier parcels cost
1.50 per extra kilogram. Express delivery doubles the price.
"""
cost = 4.95 + max(0, weight_kg - 2) * 1.50
return cost * 2 if express else cost
print(net_price(121))
print(net_price.__doc__)
print(shipping_cost(4))
100.0 Return the price without VAT, rounded to cents. 7.95
Type hints
A type hint (or annotation) states
the type a parameter, return value or variable is expected to have.
You write it after a colon for parameters and variables, and after
-> for the return value.
def average(values: list[float]) -> float:
return sum(values) / len(values)
def stock_report(stock: dict[str, int]) -> str:
return ", ".join(f"{item}: {count}" for item, count in stock.items())
def find_price(prices: dict[str, float], item: str) -> float | None:
return prices.get(item)
print(average([18.5, 21.0]))
print(stock_report({"pens": 40, "paper": 12}))
print(find_price({"pen": 1.25}, "stapler"))
19.75 pens: 40, paper: 12 None
-
Built-in types are written as themselves:
int,float,str,bool. -
Containers name their contents in square brackets:
list[int],dict[str, int](key type, value type),tuple[float, float]. -
X | Nonemeans "anX, orNone" — typical for a function that may find nothing. Older code writes this asOptional[X], from thetypingmodule.
Hints are not enforced
Python stores type hints but does not check them when the program runs. A wrong type passes silently, and the result may surprise you:
def repeat(text: str, times: int) -> str:
return text * times
print(repeat("ab", 3))
print(repeat(3, 2))
ababab 6
The value of hints lies in what they tell readers and tools. Editors use them for autocompletion and warnings, and a static type checker such as mypy reads your code without running it and reports mismatches:
mypy repeat.py
repeat.py:6: error: Argument 1 to "repeat" has incompatible type "int"; expected "str" [arg-type] Found 1 error in 1 file (checked 1 source file)
Linters and formatters
You do not have to check style by hand. Two kinds of tool do it for you; they are third-party packages, installed with pip when a project wants them.
- A linter analyzes source code without running it and reports style violations and likely bugs: unused imports, undefined names, a variable that is never used. Well-known linters are Ruff, flake8 and Pylint.
-
A formatter rewrites your code into one fixed
layout, so nobody has to think about spaces and line breaks.
Black and Ruff (with
ruff format) are the common ones.
Most editors can run both automatically on save. In a team, the
chosen settings are usually stored in the project's
pyproject.toml file, so everyone gets the same result.
Linters check style; tests check behavior. How to write and run
tests in Python is on
Unit Testing with pytest.
The Zen of Python
The Zen of Python is a set of
19 short principles for Python's design, written by Tim Peters and
published as PEP 20. Python prints them when you run
import this. The first lines read:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
They are not rules a tool can check, but they explain many of
Python's choices — and many of the habits on this page. Run
import this in the REPL to read all of them.
Good habits
Small functions with one job
A function that does one thing is easy to name, to test and to reuse. If you need "and" to describe a function (reads the file and calculates totals and sends an email), split it. See functions.
No magic numbers
A magic number is a bare value in the code whose meaning the reader has to guess. Give it a name as a constant: the code explains itself, and a change happens in one place.
FREE_SHIPPING_THRESHOLD = 50.0
SHIPPING_COST = 4.95
def order_total(subtotal):
if subtotal >= FREE_SHIPPING_THRESHOLD:
return subtotal
return subtotal + SHIPPING_COST
print(order_total(30.0))
print(order_total(75.0))
34.95 75.0
Compare this with if subtotal >= 50: and
return subtotal + 4.95, which leave the reader to guess
what 50 and 4.95 stand for.
Don't repeat yourself
When the same lines appear in several places, move them into a function or a loop. Duplicated code has to be fixed in every copy, and sooner or later one copy is forgotten. This principle is known as DRY: don't repeat yourself. Strictly, DRY is about duplicated knowledge, such as a rate or a rule; two pieces of code that only look alike but change for different reasons can stay separate (see DRY: don't repeat yourself).
Use version control
Keep every project in a version control system such as Git, and commit small, working steps with a clear message. You can then see what changed and why, undo mistakes, and work together without overwriting each other's work. Never commit passwords, API keys or a virtual environment folder.