Code is read far more often than it is written. The line you type today will be read by a teammate tomorrow, by you in six months, and by a reviewer deciding whether it is safe to ship. Python gives you enormous freedom — you may name a variable x, X, or data_2 — and that very freedom is why a shared style matters.
PEP 8 is Python's official style guide. It is not part of the language; the interpreter runs ugly code perfectly well. PEP 8 is a set of agreements about how to lay code out so that any Python programmer can absorb it at a glance. Following it is what separates a script that merely works from a program a whole team can maintain.
This lesson covers the conventions you will meet every day: indentation, line length, blank lines, imports, and the naming rules that turn identifiers into documentation.
flowchart TD R["Readable code"] --> A["Layout"] R --> B["Naming"] R --> C["Whitespace"] R --> D["Structure"] style R fill:#3776ab,color:#fff style A fill:#1e293b,color:#fff style B fill:#1e293b,color:#fff
Four spaces, consistently
The rule you feel first is indentation. PEP 8 prescribes four spaces per level and asks that a file use one indent character throughout. Python itself is stricter still: mixing tabs and spaces in a single block raises a TabError before a line of your code runs. Consistent indentation is therefore not just style — it is the prerequisite for execution.
Line length is the other layout rule people argue over. PEP 8 suggests 79 characters as a limit, with a relaxed 99 acceptable for teams that prefer it. The exact number matters less than the habit: a line so long it wraps unpredictably on a reviewer's screen is hard to read and hard to discuss. When an expression grows long, split it inside parentheses rather than at an arbitrary point.
def weekly_pay(hours, rate):
"""Return gross pay for a week, including overtime."""
if hours > 40:
overtime = (hours - 40) * rate * 1.5
return 40 * rate + overtime
return hours * rate
print(weekly_pay(40, 20))
print(weekly_pay(45, 20))
Names that explain themselves
PEP 8's naming rules pay off every single day, because good names make code self-documenting. Three shapes cover almost everything you will write:
- snake_case for functions, methods, and variables —
total_price,user_count. - PascalCase for classes —
ShoppingCart,NetworkClient. - UPPER_CASE for constants —
MAX_RETRIES,DEFAULT_TIMEOUT.
The shape tells you what a name is before you read the line it sits on. When you meet MAX_RETRIES = 3, the capitals announce that this value does not change; when you write client = NetworkClient(), the capital in the class and the lowercase in the variable line up exactly as a reader expects.
Favour whole words, too. usr_cnt saves four keystrokes and costs a reader a beat of decoding every time it appears. user_count costs nothing and reads instantly.
flowchart LR
subgraph V["snake_case"]
V1["functions & variables"] --> V2["total_price"]
end
subgraph C["PascalCase"]
C1["classes"] --> C2["ShoppingCart"]
end
subgraph U["UPPER_CASE"]
U1["constants"] --> U2["MAX_RETRIES"]
end
style V2 fill:#3776ab,color:#fff
style C2 fill:#b45309,color:#fff
style U2 fill:#1e293b,color:#fff
MAX_CAPACITY = 10
class Cart:
def __init__(self):
self.items = []
def add(self, product):
if len(self.items) < MAX_CAPACITY:
self.items.append(product)
cart = Cart()
cart.add("book")
cart.add("lamp")
print(len(cart.items))
Whitespace and breathing room
Around operators, PEP 8 asks for a single space on each side — total = price + tax, never total=price+tax — and no space directly inside brackets, so items[0] rather than items [0]. These rules look petty in isolation, yet a file where every operator is spaced identically reads as one calm voice instead of visual noise.
Blank lines carry meaning as well. Two blank lines separate top-level functions and classes; one blank line separates methods inside a class. That rhythm is how the eye finds the boundary of a unit of code before it reads a single word of it.
Imports at the top
PEP 8 places every import at the top of the file, one per line, in a fixed order: the standard library first, then third-party packages, then your own modules, with a blank line between groups. Gathering imports in one predictable place means a reader can see everything a module depends on before any logic begins.
Sorting them alphabetically within each group keeps changes tidy: when two people each add an import, the lines land in a stable position rather than wherever each person happened to type.
flowchart TD F["a clean .py file"] --> S1["module docstring"] F --> S2["imports<br/>stdlib then third-party"] F --> S3["constants"] F --> S4["classes & functions"] style F fill:#3776ab,color:#fff
import math
PI_APPROX = math.pi
def circle_area(radius):
return PI_APPROX * radius ** 2
print(circle_area(2))
Write average_speed(distance_km, time_hours) that returns the average speed. Use a clear snake_case name and a descriptive parameter for each value, and return rather than print.
def average_speed(distance_km, time_hours):
# distance over time
pass
Divide distance_km by time_hours.
Use return, not print, so the caller receives the value.
def average_speed(distance_km, time_hours):
return distance_km / time_hours
A constant defined at the top of a module. Predict what this prints.
MAX_SCORE = 100 score = 75 print(score <= MAX_SCORE)
MAX_SCORE holds 100.
Is 75 less than or equal to 100?
This function is meant to return the total of a list of prices, but it crashes with an IndentationError before it runs — the body is not indented under the def. Fix the indentation so it runs.
def total_price(prices):
prices_total = sum(prices)
return prices_total
Every line of the body sits four spaces to the right of 'def'.
sum([10, 20, 5]) is 35.
def total_price(prices):
prices_total = sum(prices)
return prices_total
Write format_price(amount) that returns a price string with a currency sign and exactly two decimals, so format_price(9.5) returns "$9.50".
def format_price(amount):
# build a string like "$9.50"
pass
An f-string with a format spec: f"${amount:.2f}".
:.2f rounds to two decimal places.
def format_price(amount):
return f"${amount:.2f}"
Which name follows PEP 8 for a variable holding the number of active users?
Variables and functions use snake_case — lowercase words separated by underscores. PascalCase is for classes, UPPER_CASE for constants, and dropping the underscore hurts readability.
Recap
- PEP 8 is Python's style guide — conventions, not law, but following them makes code readable to any Python programmer.
- Indent four spaces, consistently; mixing tabs and spaces is a
TabError, not a warning. - Name by shape: snake_case for functions and variables, PascalCase for classes, UPPER_CASE for constants.
- One space around operators, none inside brackets; two blank lines between top-level definitions.
- Imports go at the top, grouped: standard library, third-party, local.
Next you will turn these readable building blocks into idiomatic Python — the patterns experienced Python programmers reach for without thinking.