Master

Design Patterns in Python

Apply common design patterns using Pythonic idioms.

What you will see

16 12.566370614359172

About 2 min of reading

Try It

Apply common design patterns using Pythonic idioms: singletons via `__new__`, factories as dict lookups, and strategies as a dict of callables. Each step builds a small working example.

1

Create a singleton that returns the same instance every time

class Config: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._settings = {} return cls._instance a = Config() b = Config() print(a is b)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
True
2

Share state between all instances of a singleton

class Config: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._settings = {} return cls._instance c = Config() c._settings["theme"] = "dark" c2 = Config() print(c2._settings["theme"])

Before you run it, say out loud what it will print. Then open the answer.

What it prints
dark
3

Build objects from a factory dict lookup

class Circle: def shape(self): return "circle" class Square: def shape(self): return "square" def create(shape): shapes = {"circle": Circle, "square": Square} return shapes[shape]() obj = create("circle") print(obj.shape()) other = create("square") print(other.shape())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
circle square
4

Use a strategy dict to swap algorithms

def area_square(s): return s ** 2 def area(kind, size): return {"square": area_square}[kind](size) print(area("square", 4))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
16
5

Add another strategy to the same dict

import math def area_square(s): return s ** 2 def area_circle(r): return math.pi * r ** 2 STRATEGIES = {"square": area_square, "circle": area_circle} def area(kind, size): return STRATEGIES[kind](size) print(area("square", 4)) print(area("circle", 2))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
16 12.566370614359172

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

Patterns restructure how objects are created, composed, or called to reduce coupling and reuse behaviour.
1 Caller asks a factory/creator for an object
↓
2 Factory returns a concrete implementation
↓
3 Caller invokes via a shared interface
↓
4 Strategy swaps the algorithm at runtime
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

class Config: sets nothing on screen
_instance = None sets nothing on screen
def __new__(cls): sets nothing on screen
if cls._instance is None: sets nothing on screen
cls._instance = super().__new__(cls) sets nothing on screen
cls._instance._settings = {} sets nothing on screen
return cls._instance sets nothing on screen
a = Config() sets nothing on screen
b = Config() sets nothing on screen
print(a is b) True
Why this works
Pythonic pattern philosophy

Python favors simplicity over formal patterns. Many GoF patterns collapse into language features: dependency injection is just a function argument, observers are callbacks, and factories are dict lookups. Reach for a formal pattern only when it clarifies intent rather than to match a template.

When you have a choice

When to use a pattern

Use a pattern when duplication or coupling grows painful. A factory removes if/else on type; a strategy isolates changing algorithms; a decorator adds behaviour without subclassing. Prefer composition over inheritance: build behaviour from small composable pieces.

Examples

Singleton with a class

One instance only

class Config:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._settings = {}
        return cls._instance

__new__ controls instance creation. Use it sparingly; modules already act as singletons in Python.

Factory pattern

Build objects by type

def create(shape):
    shapes = {
        "circle": Circle,
        "square": Square,
    }
    return shapes[shape]()

obj = create("circle")

A factory centralises object creation and keeps callers decoupled from concrete classes.

Strategy with functions

Swappable algorithms

import math

def area_circle(r): return math.pi * r ** 2
def area_square(s): return s ** 2

def area(kind, size):
    return {"circle": area_circle, "square": area_square}[kind](size)

print(area("square", 4))   # 16

Because functions are first-class, the Strategy pattern is often just a dict of callables.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Metaprogramming and Decorators
Previous Lesson
Metaprogramming and Decorators