Python Best Practices — PEP 8, Clean Code
The conventions that separate readable, maintainable Python from code that works but nobody wants to touch.
A Style Guide, Not a Language Rule
PEP 8 is Python's official style guide — a document, not a language feature. Code that violates every PEP 8 rule still runs perfectly fine; the value of PEP 8 is entirely social and practical: it means any two Python developers, from different companies, different countries, who have never met, can read each other's code without a mental translation step, because formatting conventions are shared rather than personal.
# PEP 8 compliant
def calculate_total(price, tax_rate):
return price * (1 + tax_rate)
# Not PEP 8 compliant — still runs identically, just harder for anyone else to read
def calculateTotal(price,tax_rate):
return price*(1+tax_rate)snake_case, PascalCase, and SCREAMING_SNAKE_CASE
# snake_case — functions, variables, methods, modules
def calculate_total(price, tax_rate):
user_name = "Asha"
# PascalCase — classes
class PaymentProcessor:
...
# SCREAMING_SNAKE_CASE — constants (values that never change)
MAX_RETRIES = 3
DEFAULT_TIMEOUT_SECONDS = 30
# _leading_underscore — internal/non-public (covered fully in the Encapsulation module)
class Account:
def __init__(self):
self._internal_cache = {}These are not arbitrary — camelCase for functions and snake_case for classes would technically run without any error, but it would instantly signal "this code was written by someone unfamiliar with Python conventions" to any experienced reviewer, in the same way unusual punctuation stands out in prose even when the sentence is grammatically valid.
user_list is weaker than users (the type is usually obvious from context or a type hint); calculate_and_return_total is weaker than just calculate_total (every function "returns" something — the word adds nothing). Favour names a reader can understand without opening the function body.Line Length, Whitespace, and Blank Lines
# 4 spaces per indentation level (never tabs) — covered back in Control Flow
if condition:
do_something()
# Max line length: 79 characters (many teams relax this to 99-120 in practice,
# but PEP 8's own recommendation is 79 — check your project's actual configured limit)
# Two blank lines between top-level function/class definitions
def first_function():
...
def second_function():
...
class MyClass:
...
# One blank line between methods inside a class
class Account:
def deposit(self, amount):
...
def withdraw(self, amount):
...# Preferred
total = price * quantity
result = (a + b) * (c - d)
# Avoid
total=price*quantity
result = ( a+b ) * ( c-d )Three Groups, in a Consistent Order
# 1. Standard library imports
import json
import os
from datetime import datetime
# 2. Third-party imports
import requests
import pandas as pd
# 3. Local/project imports
from myapp.models import User
from myapp.utils import format_currencyEach group is conventionally separated by a blank line, and alphabetised within the group. Tools like isort automate this entirely — running it as part of a project's formatting pipeline means nobody has to manually maintain import ordering by hand.
Documenting What a Function Does, Not How
def calculate_discount(price, percent):
"""Apply a percentage discount to a price.
Args:
price: The original price, in dollars.
percent: The discount percentage, from 0 to 100.
Returns:
The discounted price, rounded to 2 decimal places.
Raises:
ValueError: If percent is outside the 0-100 range.
"""
if not 0 <= percent <= 100:
raise ValueError(f"Invalid discount percent: {percent}")
return round(price * (1 - percent / 100), 2)A docstring documents the function's contract — what it expects, what it returns, what can go wrong — not its internal implementation, which the code itself already shows. Several docstring formats exist (Google-style, shown above; NumPy-style; reST) — the specific format matters far less than a team picking one and staying consistent, since tools that auto-generate documentation from docstrings expect one recognisable format throughout a codebase.
import this — A Real Design Philosophy, Not Just a Joke
>>> import this
The Zen of Python, by Tim Peters
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.
...It reads like a novelty at first, but several of its lines map directly onto concrete patterns already covered throughout this track: "Explicit is better than implicit" is exactly why from module import * is discouraged (the Modules module) — it hides where names actually come from. "Flat is better than nested" is precisely the guard-clause pattern from Control Flow, restructuring deeply nested conditionals into flat, early-return logic. "There should be one — and preferably only one — obvious way to do it" is why idiomatic Python conventions (like preferring if items: over if len(items) > 0:) matter beyond personal taste — consistency across a codebase and across the whole language community is itself valuable.
Style Guides Are Guidelines, Not Laws
# Smell: a function doing too many unrelated things
def process_order(order):
validate(order)
charge_card(order)
send_email(order)
update_inventory(order)
log_analytics(order)
# Better: split into smaller, focused functions — each independently
# testable and each with a name describing exactly one responsibility
# Smell: "magic numbers" with no explanation
if attempts > 3:
lock_account()
# Better: name the constant, and its meaning becomes self-documenting
MAX_LOGIN_ATTEMPTS = 3
if attempts > MAX_LOGIN_ATTEMPTS:
lock_account()Linters and Formatters — Tools, Not Memorisation
pip install black ruff
black your_project/ # auto-formats code to a consistent style — no debate needed
ruff check your_project/ # lints for style issues, unused imports, common bugs, and moreblack is an "opinionated" formatter — it makes formatting decisions for you (line breaks, quote style, spacing) with almost no configuration, specifically to end bikeshedding over formatting preferences within a team. ruff is a fast linter that catches style violations, unused imports, and a range of likely bugs, and has largely replaced older tools like flake8 and pylint in new projects due to its speed. Most real teams wire both into CI, so style consistency is enforced automatically rather than relying on every engineer memorising every rule.
A Code Review Slowed by Style Debates, at a Kansas City Startup
A small team notices that a large fraction of their pull request review comments are about formatting — spacing, line length, whether a string should use single or double quotes — rather than actual logic, correctness, or design. Reviewers spend real time on comments that add no value to the software itself, and authors get frustrated re-litigating style preferences that are ultimately arbitrary.
# Added to CI, blocking merge on failure:
black --check .
ruff check .
# Added to pre-commit hooks, so formatting issues never even reach a PR:
pip install pre-commit
pre-commit installThe actual effect on the team's process
Style-related review comments dropped to nearly zero within two weeks — not because engineers suddenly became more careful, but because black and ruff now caught and auto-fixed nearly everything before a human reviewer ever saw the code. Code review time genuinely shifted toward the things that actually needed a human's judgement — architecture, edge cases, whether the logic was correct — which the team considered the single highest-leverage process change they made that quarter.
Four Misconceptions About Python Style
5 Interview Questions — With Complete Answers
Style Mistakes Beginners Make Constantly
Issues You Will Hit With Style Tooling — And Exactly Why
🎯 Key Takeaways
- ✓PEP 8 is a style guide, not a language rule — it exists so any Python developer can read unfamiliar code without a mental translation step, not because the interpreter requires it.
- ✓snake_case for functions/variables/modules, PascalCase for classes, SCREAMING_SNAKE_CASE for constants — mixing conventions signals unfamiliarity even though the code still runs.
- ✓A docstring documents a function's contract (inputs, output, possible exceptions) — not its internal implementation, which the code body already shows.
- ✓The Zen of Python (import this) maps onto concrete patterns already covered in this track — explicit imports, flat guard-clause logic, one obvious idiomatic way to do things.
- ✓PEP 8 explicitly allows project-level consistency to outweigh strict adherence — matching an existing codebase's established style is often more valuable than a technically "more correct" but inconsistent change.
- ✓Tools like black (formatting) and ruff (linting) automate the mechanical parts of style, freeing code review to focus on logic, design, and correctness — genuinely shown to shift review time toward higher-value feedback.
What comes next
The final module of this track — Python Interview Prep — synthesises everything covered across all 46 modules into a focused set of real interview questions and coding patterns.
Module 46 → Python Interview PrepDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.