The Real Reason the Singleton Pattern Exists
By ArjanCodes
Summary
## Key takeaways - **Singletons: Not just global state issues**: While often criticized for introducing dangerous global shared state, singletons have another overlooked reason for their use that doesn't involve global state at all. [00:04], [00:15] - **Pythonic Singletons: Modules are better**: In Python, modules inherently provide singleton-like behavior as they are loaded only once and reused, offering a simpler, more testable, and thread-safe alternative to class-based singletons. [07:33], [08:06] - **Singleton's thread-safety issues**: The singleton pattern is not inherently thread-safe; multiple threads can create duplicate instances simultaneously, leading to unpredictable behavior and defeating the pattern's purpose. [09:54], [10:21] - **Controlled Instantiation: A hidden strength**: A useful aspect of the singleton pattern is controlled instantiation, enabling lazy loading of expensive resources like large language models, which is efficient and avoids global state issues. [12:56], [13:35] - **Avoid Singletons for explicit dependencies**: To improve testability and reduce coupling, prefer explicit dependencies by passing needed resources via constructors or parameters instead of relying on singletons. [09:38], [09:47]
Topics Covered
- Design Patterns: Understand Problems, Not Just Solutions
- Why the Singleton Pattern is a Dangerous Anti-Pattern
- Python's True Singleton: Why Modules Are Enough
- Singletons Cause Unpredictable Bugs in Multi-threaded Code
- When Singletons Shine: Controlled Instantiation for Performance
Full Transcript
Singletons are an antiattern. You've
probably heard this before. The reason
is that it introduces global shared
state. And yeah, that's dangerous. But
there's another often overlooked reason
why developers use the singleton
pattern. One that doesn't involve global
state at all. Now, in this video, I'm
going to explore both sides of the
pattern, the antiattern part and the
maybe useful reason. And at the end,
I'll show you a practical example of
where that second part of the singleton
can actually work. I'm going to be doing
a lot more design pattern videos in the
coming months. So, make sure you
subscribe so you don't miss those. And
check out the design patterns playlist
I've linked down below. It contains all
my videos covering design patterns so
far. Now, before I show you the code,
every time I do a design pattern video,
there's always someone in the comments
saying that I shouldn't turn Python into
Java or that these design patterns are
useless in Python or some variety of
that. Now, here's my response to that.
I'm not doing these videos as standard
design pattern explanations. My goal is
not to convince you to use these design
patterns all over your Python code. In
fact, I chose the singleton pattern on
purpose here because actually I don't
think you should use it. But like I
said, there is an interesting aspect to
the pattern and that is exactly my goal
with these videos to go a bit deeper
than just a tutorial. Also, I might not
always exactly follow the gang of four
book version if there is one because to
me that's not what these patterns are
about. To me, it's about understanding
the problem that a pattern tries to
solve and then hopefully that helps you
come up with a better solution when
you're running into a similar problem in
your own code. So, whenever someone
complains about me turning Python into
Java, please refer them to this video.
Now, let's start with a very basic
example of a singleton in Python without
saying whether you should use this or
not. All right. So I have a config class
here and as you can see there is a class
variable instance. So that is actually
going to be the singleton instance.
There's also an initializer that sets
some values in the instance uh in this
case a database URL and whether or not
we should uh include debugging
information or not. So the whole idea of
using a singleton here is that we have
one config object that we can use
everywhere in our code. And how does it
work in this case? Well, actually what
I've done here is override the new
dunder method and there I check hey if
there is no instance yet attached to the
class then I'm going to create a new
instance by calling the new method on
the superass because this one is
overriding that original method
otherwise we would get an endless loop.
If there already is an instance I'm not
creating it and but simply returning the
existing instance. So that means that if
I create a config object here and then I
seemingly create another one actually
these two objects are exactly the same
because this one creates the first
instance and this one simply returns a
reference to that already existing
instance. So here you see the output. We
create a new instance. The comparison s1
is s_ub_2 gives us true. And these are
the ids which are the same. There is one
object. So a config object that's a good
example of where you might want to use
something like a singleton or some other
global resource like a database
connection or logger something that you
need in multiple places in your code and
where you only need one of those things.
And that's also where things start going
wrong. You now have invisible coupling.
All these parts of the code that use
this shared in this case config object
they depend on this shared state and
it's not explicitly declared because
they will actually create an instance
like so. And that means you get all
sorts of unwanted effects. For example,
if one area of the code changes
something in the config object, you
don't expect it to affect anything else
because it's a locally created instance
or so you thought. But of course, that's
not true. So it's that change is also
going to be reflected in all the other
places. Now sometimes you might want
that, but with the singleton pattern,
it's not explicit that this is what
happens. And that's the problem with it.
And testing also becomes a nightmare
because each test may then affect the
global state which then screws up
basically everything. Now why does the
singleton actually exist? Well, it
mostly came about because of languages
like Java. So Java is very different
from Python in that it doesn't allow you
to just create a variable in a script
like you can do in Python. In Java,
everything has to be inside an object
and then you have the uh entry point of
the application which is a static main
method in a class. So that means if you
need some sort of global state, you need
a class and you need an instance and
that's what the singleton sort of tries
to solve. On top of that, you can add
some security mechanisms like hiding the
constructor, making it private, exposing
a static instance, make sure that no
other instances can ever be created. You
can do that in many objectoriented
languages including Java, C++, C, etc.
Now, Python itself doesn't support
private constructors. So if you try to
do that uh the approach kind of feels
clunky and unpythonic.
Now if you really wanted to build a
singleton class that is like generic
that you can reuse then alternatively
what you can do is use a meta class to
do something like that in Python. Here's
an example of how that would work. So I
have a class singleton that's a subclass
of type. So that makes it a meta class.
This has instances in a dictionary and
then this class has a call dumber method
with a class passed as an argument. If
that class is not yet in the instance
list, then we're creating an instance of
that class. So the singleton metaclass
takes care of all the singleton
instances of the various classes that
use this meta class. And then what you
can do is import that singleton meta
class and use it in the config class
like so. And then you simply use it like
a regular class. And then you create
your config objects as usual. You can
print them and you're going to get
exactly the same results as before.
Well, obviously the ids are now
different. So this is a more let's say
Pythonic way of doing it. But like I
said, it's not 100% safe. Python
basically always allows you to do
whatever you want. So if you do want
multiple instances, the way you can do
instead of just calling the initializer
here is that you can call the new dunder
method directly and then pass the class
name and then call the init dunder
method as well.
So when you do this now you actually
circumvent the whole metaclass singleton
thing and you still get multiple
instances. So in Python there is no way
to actually avoid this type of thing.
It's in the end it's the developer's
responsibility. However, if you want a
real Pythonic singleton there is
actually a builtin mechanism for that
and that's called a module. A module
obviously is managed by Python. It's
going to be loaded only once and then
will be reused everywhere. That's
exactly what a singleton is. Here's what
that looks like. I have fileconfig.pi pi
which has some variables in it like a
database URL the debug status whatever
and then in my example I can simply
import it and use it that's it so if all
you need is shared global state in
Python use a module you don't need a
class this is simple obvious it's
testable it's uh thread safe now of
course that's still the issue that there
is global state and that's something
that you want to avoid so what I do is
whenever I end up with global state in
my code in some way. I see if I can
modify my design to remove it without
affecting usability too much. And in
many case that's actually possible, but
you'll still likely need to create some
object somewhere that other parts of
your code need to access. So what I do
is I try to group those things as best
as possible in one place. preferably
that's the main file where you then
create the actual objects and patch up
everything. Bob Martin calls this the
dirty place in your code. And then once
you have that, that one place where you
create all those dirty, nasty objects,
then you can do dependency injection,
organizing things smartly so that you
don't end up with 10,000 arguments that
you need to pass along everywhere.
Dependency injection, by the way, is
another pattern that I'm going to take a
closer look at in a separate video. I
won't do that here because otherwise
this video becomes way too long. Now
like I mentioned, one of the biggest
issues with the singleton pattern is
that this introduces shared mutable
state. If your singleton holds any
state, it can easily leak between tests,
especially if you forget to reset it. So
that leads to tests that only pass or
fail depending on the order that they're
run in or some unexpected side effects
from previous test runs. You get tight
coupling between test and implementation
details. is not good. Instead, prefer
explicit dependencies. Pass what you
need via constructors or parameters. It
makes testing easier and keeps your
components more loosely coupled. Another
problem with the singleton is that it's
not thread safe. In multi-threaded code,
two threads could enter the singleton
creation logic at the same time and then
you'll end up with multiple instances.
Here I have an example of that. So I'm
using my simple meta class singleton. I
create an unsafe subclass of that. And
then I'm starting a bunch of threads.
And then we're going to see how often
this initializing is going to be printed
when I run this. As you can see now, it
creates multiple instances of this
singleton. And depending on when it's
run, it's going to create just one or uh
two or three or more. We we never know
basically what is going to happen. It's
actually pretty neat random number
generator but not really what you want.
And of course if this happens that
defeats the whole purpose of the pattern
and actually this exact thing happened
to me. I remember when I was working for
the university, I was building some sort
of uh game graphics engine that we would
use for research and we use singleton
patterns because I was an old-fashioned
researcher and I use that for things
like managing resources in our
environments, loading textures, that
kind of stuff. And you only want one of
those. But games and game engines are
typically the kind of thing where you
will have multiple threads, right? one
for rendering, another thread for
updating the UI, another thread for
listening to user input, etc. And it was
actually a huge problem because we ended
up with loading resources 10 20 times
when we really didn't understand why
that was happening until we realized,
oh, wait a minute, we're using the
singleton pattern and that is actually
not thread safe. So we actually have
multiple instances of that and that kind
of broke the whole thing. Now if you
want to fix this it means you need to
add some sort of locking or
synchronization mechanism inside the
meta class and this is actually
possible. Here I have a version of the
singleton meta class that has locks
included in it. So I have my instances
just like before and then I have logs
and I have a global lock. So the global
lock ensures that we have one lock per
class. And then next we use that log to
create the instance of the class that
we're looking for. And then when we use
the safe version of the singleton and I
create my threads again and I run this,
you see that we always get a single
instance of the singleton. So it's
thread safe. But of course now we had to
add like a bunch of locks and everything
and it just slows down the code even
more. So it's possible to do this. But
if you're working in a multi-thread
environment, be very careful with global
state like this. In fact, I highly
suggest you avoid them entirely unless
you're absolutely sure that you need it.
Now, like I mentioned at the start of
the video, there is another aspect of
the singleton pattern that has actually
nothing to do with global state. But
before I show you that, if you're
designing a project from scratch, there
are some things you shouldn't forget
because they'll come back to bite you
later. I have a free guide that walks
you through the seven steps I follow
when designing software. Grab your copy
at r.code/design guide. The link is also
in the video description. Now, what is
that other aspect of the singleton
that's actually useful? It's not global
state. It's that you have control over
instantiation.
Instantiation, that's a weird word, but
it can sometimes be useful. Here's an
example. Let's say you have some
expensive resource that you want to load
only when it's needed. and only once.
For example, say your code depends on a
large language model. Of course, you
don't want to load that large language
model every time you want to use it,
right? You want to do that only once.
So, here's what you can do. You could
create a model loader which is a
singleton class uses the meta class and
then uh in the initializer it actually
loads the model. Now, I'm just printing
something but there you could actually
load that very large model which takes a
long time. And then what you can do in
your code is you can create instances of
this model loader and do a prediction
based on the data. So the model is now
loaded only when you use it for the
first time. So that means you don't have
to load it when the app starts up which
is going to slow down things. It will
only load when you actually need it.
Basically lazy loading. So there's no
global state here. Model loader doesn't
have any instance variables. There's no
testability issues here. And there's no
magic. This is what controlled
instantiation is and that can be quite
useful. And next to that there is a more
general version of the singleton pattern
that could actually also be useful and
that's called the object pool. And
that's basically a pattern where you
don't have one instance but you manage a
pool of reusable instances rather than
that single one. If you want me to do a
full video about the object pool, let me
know in the description. Now I'd like to
hear what you think. Do you use
singletons in your codebase? Have you
used them in the past? Do you use the
pattern in other languages than Python?
What are elements of this pattern that
you find interesting? Let me know in the
comments. Like I said, there's a
playlist with all my design pattern
videos up until now. Link is down below.
One that I found particularly
interesting is the builder design
pattern. If you want to learn more about
that, check out this video next. Thanks
for watching and see you next
Loading video analysis...