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.
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.
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.
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.
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.
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.
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