Tai Kedzierski
Posted on May 10, 2022
Object orientation
The typical example of object orientation goes like this:
- You have a concept of a thing, which you define in your class, like, the idea of "a car" (
class Car
) - You have an object instance of a class, representing a tangible item relating to that concept, like a Renault Laguna (
new Car("Renault", "Laguna")
This makes sense - we have a concept of a car being drivable, ridable, etc which applies to all actual cars.
In the language of object orientation, it began to be stated that everything is an object (and by extension, everything has a class).
This falls down in some scenarios in the real world:
Let's say we're working on a project on a Raspberry Pi. And that Pi has access to the GPIO board (for simplicity, there is a board, it has electric circuits that can be closed or opened). And let's, arbitrarily but not unreasonably, state that the Pi is only ever has one set of GPIO pins on it at a time.
Singleton
If we were doing this in Java, we'd probably model class GpioBoard extends Singleton
(or however we do that in Java in reality - treat as pseudo-code) because we don't want several instances of GpioBoard
being retrieved and configured differnetly in different parts of the program. The effect of extending Singleton
means that when the class tries to instantiate, the environment checks for an existing instance to return, and only creates a new one if none such already exists.
With such an approach in Python, we end up with a class in which we need to implement the singleton pattern explicitly, an instantiation, and a call
# FILE: gpio.py
class GpioBoard(Singleton):
_instance = None
def __new__(cls):
# From https://python-patterns.guide/gang-of-four/singleton/
if cls._instance is None:
print('Creating the object')
cls._instance = super(Logger, cls).__new__(cls)
# .... initialization code ...
return cls._instance
def set_pin(self, value):
... # implementation
# FILE: main.py
from gpio import GpioBoard
board = GpioBoard()
board.set_pin(1)
A module is a singleton
Here's the thing though - in Python a module itself is a singleton. The following code does exactly the same, without the additional code:
# FILE: gpio.py
def __setup():
# .... initialization code ...
def set_pin(value):
... # implementation
# Single runtime entrypoint only gets executed once
# even from multiple imports of the same module
__setup()
# FILE: main.py
import gpio
# Immediately ready to use, would you look at that ...
gpio.set_pin(1)
So much less code. No singleton management. Module is an inherent singleton.
What if there are multiple GPIO extension boards?
Or more generally, what if the item that was a singleton in the original understanding of the problem/design of the architecture becomes no longer a singleton ?
In this case, there may yet be re-work to be done. An architectural change can cause base assumptions to become false in later iterations of a design, and re-working what were once module-level variables and methods into a regular object-oriented design becomes a little (or huge) juggling task ; and anything that once simply imported our library now would need to be updated to get a class and instantiate an object explicitly.
Do we need singletons in Python?
I actually set out to deliver an emphatic "no" to the question. From the point of view of "are singletons the answer to enforcing single-instance (in Python)?": no, using a module and module-level methods without a class definition is perfectly satisfactory.
But I don't really have a firm yes/no answer at this point. Architectures being fluid, it may yet make sense to implement everything as an object from the get-go , and be able to de/singleton-ise back and forth as needed. Is explictly implementing singleton patterns a case of YAGNI? Who knows.
Posted on May 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.