Clean Code and Design Patterns
Habits of top Python developers.
Code is read far more often than it is written. Six months from now, Ava will read her own code, and wonder who wrote it. Clean code is kind to your future self, and to your teammates. In this lesson, we cover the style guide, the principles that keep code healthy, and a few design patterns, that solve common problems.

- Code is read more than written
- Kindness to your future self
- Principles and patterns that last
PEP 8 and naming
Python has an official style guide, called PEP eight. Use four spaces for indentation. Use snake case for functions and variables, capitalised words for classes, and upper case for constants. Keep lines under about eighty eight characters. Tools like black and ruff format and check your code automatically, so you never argue about style.
Before and after
Compare two versions of the same function. The first uses cryptic names, deep nesting, and magic numbers. The second uses clear names, early returns, a named constant, and a docstring. It does the same job, but it can be understood in seconds. Aim for the second, every time.
Hard to read
- def f(a, b):
- if a: if b: ... nested
- magic number 0.2
- no docstring
Clean
- def total_with_tax(price, qty):
- early return, flat code
- TAX_RATE = 0.2
- clear docstring
The Zen of Python
Python has a poem about its philosophy. Import this prints it. Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Readability counts. If the implementation is hard to explain, it is a bad idea. When in doubt, reread these lines, and choose the simpler path.
import thisThe 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!
Design pattern: Strategy
Design patterns are named solutions to common problems. In Python, functions are objects, so many patterns become short. In the strategy pattern, you pass in the behaviour you want, as a function. Here, the same checkout works with different discount strategies, without any if chains.
def regular(total): return total
def student(total): return total * 0.9
def vip(total): return total * 0.8
def checkout(total, discount=regular):
return round(discount(total), 2)
print(checkout(50))
print(checkout(50, student))
print(checkout(50, vip))50 45.0 40.0
Design pattern: Factory and Singleton
A factory function creates the right object, from a name, so callers do not need to know the class. In Python, a dictionary of classes does the job neatly. A module level object is already a singleton, since a module is imported only once. Keep patterns light, they exist to help, not to impress.
class Fern:
def water(self): return "every 3 days"
class Cactus:
def water(self): return "every 3 weeks"
PLANTS = {"fern": Fern, "cactus": Cactus}
def make(kind):
return PLANTS[kind]()
print(make("fern").water())
print(make("cactus").water())every 3 days every 3 weeks
SOLID, in plain words
Five principles guide good design. A class should have one reason to change. Extend behaviour, without editing old code. Subclasses must work wherever the parent works. Keep interfaces small. And depend on abstractions, not on details. You do not need to memorise the names. Just remember, small, focused, replaceable pieces.
Refactor for clarity
Your turn. This function is hard to read. Rewrite it, with clear names, a named constant for the tax rate, and a docstring. It must still return the same result. Ready? Here is one solution. Good names, a constant, and a docstring, make it self explaining.
Rewrite f(a, b) as total_with_tax(price, qty) with a TAX_RATE constant and a docstring.
Show hint
constants, names, docstring
Show solution
TAX_RATE = 0.2
def total_with_tax(price, qty):
"""Return the total price including tax."""
return price * qty * (1 + TAX_RATE)
print(total_with_tax(10, 3))36.0
Summary
Wonderful. You know the style guide, how to write readable code, the Zen of Python, and a few patterns, like strategy and factory. Next, we learn how to package, and share your code, with the world.
Key takeaways- PEP 8 + black + ruff
- Clear names, early returns, constants
- Strategy and factory, kept light