Design patterns are useful in software development for two main reasons:
- They give us proven ways to solve software design problems.
- They allow us (super-smart engineers) to communicate the design of our software to each other easily.
When people talk about design patterns, it will only take a minute or two before someone brings up ‘The Gang of Four’ or ‘GoF’.
This refers to the classic book Design Patterns: Elements of Reusable Object-Oriented Software, with the ‘Gang of Four’ being the authors. This ‘timeless classic’ presented 23 ways to solve common problems in C++ and Smalltalk.
During a recent review of some python code I had written for work, my reviewer suggested I use the Singleton pattern to achieve my goal, one of the patterns that the Gang of Four write about in their book.
We ended up not implementing this pattern (there are a variety of reasons many people dislike singletons), but it got me thinking about this particular pattern in Python.
In case you haven’t come across it yet, the key part of the singleton pattern is that you have only one instance object of a given class during the lifetime of a program.
Why? Well, having only one of something can be useful for logging (e.g. your entire program can have a single configured logging object that prints things out), or for a cache, or maybe for an object that holds configuration settings for your whole app.
You might have used this pattern with object relational mapping in a web application, where you have a single object whose responsibility is to interact with your database.
You might want to use this pattern if all the objects your class creates should be exactly the same. In that case, why not just create a single object that is shared around the app?
Or what if you want a single object that is the source of state for your app? Then maybe a singleton would again be a good idea.
So great! let’s talk about how to do it in python!
Singletons in Python
If you start googling Singleton in Python you will eventually come across something that looks like this:
The first time you call MySingleton(), you get back an instance of the class. If you call it again, it skips creating a new object and just returns the one that already exists. If you play around with the code in the repl.it, you can even see it has the same address in memory. It is the same object.
But let’s be honest, the code is just weird. How often do we actually use the __new__() method in Python? This is the only case I know of – in fact __new__() was specifically introduced in Python 2.4 just to allow us to use the singleton pattern in the language (and other similar design patterns)!
If you keep googling, you’ll see a lot of awkward examples like the one above that try to force Python into a box that was designed for C++ about 30 years ago.
There Must Be an Easier Way
When the GoF wrote their book on design patterns, they weren’t thinking in Python. They were designing solutions to common problems in C++. Essentially, some of these design patterns are just compensating for missing features of these low-level languages. By using a modern, high-level programming language like Python or Javascript, you can achieve the same singleton effect in a far more natural way. How? Modules.
Simply by creating a module, you are creating the same effect as the Singleton pattern, i.e. a single instance of a class that is used across your app.
In fact, the docs for Python 3 state the following:
“The canonical way to share information across modules within a single program is to create a special module (often called config or cfg). Just import the config module in all modules of your application; the module then becomes available as a global name. Because there is only one instance of each module, any changes made to the module object get reflected everywhere.”
“Note that using a module is also the basis for implementing the Singleton design pattern, for the same reason.“
Python docs (emphasis mine)
So there you go. What’s the take-away? When dealing with Python and other high-level languages, you often have access to features that render classic design pattern implementations obsolete.