Use closures for quick mini classes in Python
What are closures? #
Closures in Python are a powerful feature that allows you to create functions that can capture and remember the environment in which they were created.
This means that you can create functions that have access to variables from their enclosing scope, even after that scope has finished executing.
Here’s a simple example of a closure to illustrate the concept.
def outer():
x = 10
def inner():
print(x)
return inner
closure_func = outer()
closure_func() # prints 10
The general structure of a typical closed function is as follows:
def outer_function():
# Enclosing scope
variable = "I am from the outer function"
def inner_function():
# Inner function can access the variable from the outer function
print(variable)
return inner_function # Return the inner function
Note that if the inner needs to modify the variable from the outer function, you will need to use the nonlocal keyword.
Replace simple classes with closures.
Classes are often used to encapsulate state and behavior.
If all you need in a class is to hold some state and a single method, you can use a closure instead.
nonlocal keyword #
When a variable in the outer function needs to be modified by the inner function, you must declare it as nonlocal.
See some examples of it’s use in the following use cases.
Counter Replacement #
Here’s an example of how you can use closures to create a counter class. This class simply increments a number each time it is called.
def make_id_counter(last_id=0):
count = last_id
def next_id():
nonlocal count
count += 1
return count
return next_id
next_item_id = make_id_counter(29)
next_product_id = make_id_counter(210)
print(next_item_id())
print(next_item_id())
print(next_product_id())
## Output:
# 30
# 31
# 211
Accumulator Replacement #
Use closures to hold a running total. The function parameter also acts as the equivalent of a class init method.
Note that since Python is not limited to a single return value, the inner function is returning the rolled value and the total.
import random
def make_dice(sides):
total = 0
def roll():
nonlocal total
rolled = random.randint(1, sides)
total += rolled
return rolled, total
return roll
roll_six_sided = make_dice(6)
roll_ten_sided = make_dice(10)
roll6, total6 = roll_six_sided()
print("Rolled 6-sided die:", roll6, "Total:", total6)
roll10, total10 = roll_ten_sided()
print("Rolled 10-sided die:", roll10, "Total:", total10)
roll10, total10 = roll_ten_sided()
print("Rolled 10-sided die:", roll10, "Total:", total10)
## Output:
# Rolled 6-sided die: 1 Total: 1
# Rolled 10-sided die: 4 Total: 4
# Rolled 10-sided die: 9 Total: 13
Timer Replacement #
A simple timer class can easily be created using closures.
import time
def start_timer():
start_time_ms = time.time_ns() // 1_000_000
def elapsed():
return (time.time_ns() // 1_000_000) - start_time_ms
return elapsed
process_elapsed_time = start_timer()
time.sleep(1)
print("Elapsed time:", process_elapsed_time(), "ms")
time.sleep(1)
print("Elapsed time:", process_elapsed_time(), "ms")
## Output:
# Elapsed time: 1000 ms
# Elapsed time: 2000 ms
Conclusion #
Closures are a powerful feature of Python that allow you to create quick mini classes without the need for a full class definition.
Less boilerplate code means less room for errors and easier maintenance.