TLDW logo

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

Loading video analysis...