Python Closures · Examples
Core Python

Python Closures Examples

Short, runnable programs that show python closures in action. Read the explanation, press Run, then change the code and try your own ideas. New to the topic? Start with the Python Closures lesson.

1A tax calculator for each country

One factory builds a specialised function per tax rate. Each function remembers its own rate, and the calls read like plain function calls afterwards.

A tax calculator for each country
def make_tax_calculator(rate):
    def with_tax(price):
        return round(price * (1 + rate), 2)
    return with_tax

uk_vat = make_tax_calculator(0.20)
de_vat = make_tax_calculator(0.19)
ca_gst = make_tax_calculator(0.05)

for price in [20, 80]:
    print(price, uk_vat(price), de_vat(price), ca_gst(price))
Expected output20 24.0 23.8 21.0
80 96.0 95.2 84.0

2A running average that remembers a list

The remembered list is changed with append, never reassigned, so no nonlocal is needed. Each averager has its own list.

A running average that remembers a list
def make_average():
    scores = []
    def add(score):
        scores.append(score)
        return sum(scores) / len(scores)
    return add

maths = make_average()
print(maths(80))
print(maths(90))
print(maths(70))

art = make_average()
print(art(100))
Expected output80.0
85.0
80.0
100.0

3The same job as a closure and as a class

Both trackers give identical answers. The class exposes best as an attribute anyone can read; in the closure best is private and can be reached only through the function.

The same job as a closure and as a class
def make_tracker():
    best = None
    def record(score):
        nonlocal best
        if best is None or score > best:
            best = score
        return best
    return record

class Tracker:
    def __init__(self):
        self.best = None

    def record(self, score):
        if self.best is None or score > self.best:
            self.best = score
        return self.best

closure_tracker = make_tracker()
class_tracker = Tracker()
for score in [62, 75, 70, 91, 88]:
    print(score, closure_tracker(score), class_tracker.record(score))
print("class attribute:", class_tracker.best)
Expected output62 62 62
75 75 75
70 75 75
91 91 91
88 91 91
class attribute: 91

4Fixing the loop gotcha with a small factory

In make_buttons all the lambdas share one label variable. In make_buttons_fixed, every call of make_button has its own label, so each function remembers a different one.

Fixing the loop gotcha with a small factory
def make_buttons(labels):
    buttons = []
    for label in labels:
        buttons.append(lambda: f"clicked {label}")
    return buttons

def make_buttons_fixed(labels):
    def make_button(label):
        return lambda: f"clicked {label}"
    return [make_button(label) for label in labels]

names = ["Save", "Open", "Exit"]
print([button() for button in make_buttons(names)])
print([button() for button in make_buttons_fixed(names)])
Expected output['clicked Exit', 'clicked Exit', 'clicked Exit']
['clicked Save', 'clicked Open', 'clicked Exit']

5Inspecting what a closure remembers

co_freevars names the remembered variables and cell_contents shows their values (in the same alphabetical order). A function with no free variables has no closure at all.

Inspecting what a closure remembers
def make_discount(rate, minimum):
    def apply(total):
        if total >= minimum:
            return total * (1 - rate)
        return total
    return apply

summer = make_discount(0.2, 50)
print(summer.__code__.co_freevars)
print([cell.cell_contents for cell in summer.__closure__])
print(summer(80), summer(30))

def plain(total):
    return total

print(plain.__closure__)
Expected output('minimum', 'rate')
[50, 0.2]
64.0 30
None

6A closure that caches results

lookup remembers two things: the function it wraps and a dictionary of earlier answers. The second call for 12 skips the work. This is the idea behind decorators, the next lesson.

A closure that caches results
def make_cached(func):
    cache = {}
    def lookup(x):
        if x not in cache:
            print(f"computing {func.__name__}({x})")
            cache[x] = func(x)
        return cache[x]
    return lookup

def square(n):
    return n * n

fast_square = make_cached(square)
print(fast_square(12))
print(fast_square(12))
print(fast_square(5))
Expected outputcomputing square(12)
144
144
computing square(5)
25

Practise next

Python Closures exercises → Back to the lesson

Python Lambda Functions examplesPython Recursion examplesPython Variable Scope examplesPython Decorators examples