Pythonic Code & PEP 8
"Pythonic" code uses the language's features the way they were designed to be used — it's usually shorter, clearer and faster than a direct translation from another language. PEP 8, Python's official style guide, keeps code consistent across projects. Modern tools apply most of it for you.
The Zen of Python
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. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
These aphorisms, built into Python as an Easter egg, summarise its design philosophy.
PEP 8 essentials
| Rule | Example |
|---|---|
| Indent with 4 spaces | Never tabs |
| Lines up to 79 characters (many teams use 88–100) | Wrap inside brackets |
snake_case for functions and variables | total_price, get_user() |
PascalCase for classes | ShoppingCart |
UPPER_CASE for constants | MAX_RETRIES = 3 |
| Spaces around operators and after commas | x = a + b, f(a, b) |
No spaces around = in keyword arguments | f(size=10) |
| Two blank lines between top-level functions/classes | One blank line between methods |
| Imports at the top: standard library, third-party, local | One group per block |
Let tools do the formatting
Instead of memorising style rules, use a formatter and a linter. Ruff is a very fast tool that does both:
pip install ruff ruff format . # reformat code consistently (Black-compatible style) ruff check . # find bugs, unused imports, style problems ruff check --fix . # fix what can be fixed automatically
Configure your editor to format on save, and style debates disappear from code review.
Pythonic idioms
Loop over items, not indexes
names = ["Ana", "Ben", "Cleo"] # Not Pythonic for i in range(len(names)): print(i, names[i]) # Pythonic for i, name in enumerate(names): print(i, name)
0 Ana 1 Ben 2 Cleo 0 Ana 1 Ben 2 Cleo
Use truthiness and in
items = [] status = "closed" if not items: print("empty") if status in ("closed", "archived"): print("inactive")
empty inactive
Comprehensions over map/filter with lambdas
prices = [12.5, 7.0, 30.0] discounted = [p * 0.9 for p in prices if p > 10] print(discounted)
[11.25, 27.0]
Unpacking and swapping
point = (3, 4)
x, y = point
a, b = 1, 2
a, b = b, a
first, *rest = [10, 20, 30]
print(x, y, a, b, first, rest)
3 4 2 1 10 [20, 30]
Context managers for resources
from pathlib import Path with open("data.txt", "w", encoding="utf-8") as f: f.write("pythonic") print(Path("data.txt").read_text(encoding="utf-8"))
pythonic
dict.get, setdefault and Counter
from collections import Counter words = "a b a c b a".split() print(Counter(words).most_common(1)) config = {} print(config.get("timeout", 30))
[('a', 3)]
30f-strings and join
name, total = "Ana", 13.5 print(f"{name} owes ${total:.2f}") print(", ".join(["red", "green", "blue"]))
Ana owes $13.50 red, green, blue
Use built-ins
scores = [72, 88, 95] print(sum(scores), max(scores), sorted(scores, reverse=True), any(s > 90 for s in scores))
255 95 [95, 88, 72] True
Habits that prevent bugs
- Compare to
Nonewithis:if value is None. - Never use mutable default arguments (lesson 29).
- Catch specific exceptions, never a bare
except:(lesson 41). - Don't shadow built-ins like
list,id,input. - Use
pathlibfor paths and always passencoding="utf-8"for text files. - Add type hints to function signatures (lesson 33) and run a type checker.
Readable over clever
Pythonic doesn't mean squeezing everything onto one line. A three-level nested comprehension with a walrus operator might be "clever", but a simple loop with a well-named variable is easier to read and debug. When choosing between two ways, pick the one a teammate will understand fastest.
Try it yourself
Rewrite this un-Pythonic code using the idioms above, then compare with the version underneath: