AI Hints
Home›Python Tutorial›Modern Python and Clean Code
Python · Lesson 45 of 49

Clean Code and Design Patterns

Habits of top Python developers.

⏱ 2 min read▶ Video at 2:15:33🎓 Free
Watch this lesson in the Python courseWatch on YouTube ↗Full Python course

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.

Clean Code and Design Patterns
From the video: Clean Code and Design Patterns
  • 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.

4 spaces, no tabsconsistent indentation
snake_case / CapWords / UPPERfunctions, classes, constants
Short linesabout 88 characters
black and ruffformat and lint automatically

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.

zen.pypython
import this
Output
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!

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.

strategy.pypython
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))
Output
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.

factory.pypython
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())
Output
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.

One job per classone reason to change
Open to extensionadd, do not edit
Substitutable subclasseschild works where parent does
Small interfacesno unused methods
Depend on abstractionspass things in

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.

✍️ Exercise: Refactor for clarity

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
refactor.pypython
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))
Output
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
Next up: Packaging and Publishing — share your code with the world