Image

The Python Coding Stack: Creating a Singleton Class in Python And Why You (Probably) Don’t Need It

If you spend long enough in the programming world, you’ll come across the termsingletonat some point. And if you hadn’t seen this term yet, well, now you have!

In Python, you don’t need singleton classes. Almost never. But creating one is a great exercise in understanding how Python creates objects. And discussing alternatives to a singleton class helps you explore other aspects of Python.

“Hey Stephen, sounds great, but you haven’t told us what thissingletonthing is yet!”

Fair point – here we go.

What’s a Singleton?

The singleton pattern is a design pattern in which a class can produce only one instance. Not two, not three, just one. And each time youtryto create a new instance of a singleton class, you get the same object again.

Let me pick a trivial example of when you may need this. You’re writing a game. Perhaps several players can play games simultaneously. And you need a leaderboard. But you only want one leaderboard. You don’t want each game to create its own leaderboard. Otherwise, what’s the point of the leaderboard?

There are other examples in programming when singletons appear to be the solution: creating a connection to a database or to a hardware device – you only want one connection – creating a logger or a configuration manager. But they sound too serious and proper. So I’ll stick with the leaderboard in a game example for this discussion.

Creating a Leaderboard • First Attempt

Let’s say you have aGameclass and you createGameinstances each time a game is played. I won’t write this class as it’s not needed, and I want to keep this article relatively brief (famous last words!).

TheGameclass needs to access a leaderboard. EachGameinstance needs to access a leaderboard – the same leaderboard. Let’s say you create a class to manage this leaderboard:

You add the.scoresdata attribute, which is a dictionary, in the class’s.__init__()method. If this is all you need, you definitely don’t need a class. But you add some methods to manage the leaderboard:

Now you have a bit more functionality. Let’s review these methods briefly:

.add_score()adds a score to the leaderboard, as its name implies. If the player already exists in the.scoresdictionary, you add to their points tally. If the player doesn’t exist yet, you add them to the dictionary. There are neater ways to write this method, but this will do here.

.get_leaderboard()returns a sorted list containing the players in order, from those with the highest number of points to the lowest. If you’re not familiar withsorted(), itskeyparameter, andlambdafunctions, you can read one of the most popular articles onThe Python Coding Stack:The Key To The key Parameter in Python

.display()displays the leaderboard, using.get_leaderboard()along the way.

.reset()resets the leaderboard. By the way, you see why choosing descriptive names matters! I didn’t include the safety verifications and steps you might want to take for this method.

Looks good? Let’s try it out. For simplicity, you can just add these lines to the same script where you define your class:

You create an instance ofLeaderboardand call.add_score()three times. If we had aGameclass, the first line, which creates aLeaderboardinstance and assigns it to an identifier, would be included in theGame.__init__(), but here I’m just creating this variable in the main script. Here’s the displayed leaderboard:

Kate: 15 Stephen: 14

All seems fine. Kate is leading with 15 points. I’m second (also last) with 14 points.

But, later in your program, possibly within a differentGameinstance, you write the following:

And here’s the output now:

Kate: 15 Stephen: 14 
 Now Dealing With ‘another_leaderboard’ Sarah: 13 Max: 7

Recall that the first two lines of the output are from the code you wrote earlier.

But where’s Kate in the new leaderboard? And where am I? This code creates a newLeaderboardobject, unrelated to the previous one. You can confirm this by showing the identity of each object, usingid(), or by using theisoperator:

The outputs from these three calls toprint()are below:

4347130752 4347064592 False

The two objects have different identities. They’re not the same object. The identity values you get will be different from mine, but what matters here is that they’re different from each other.

You could make sure you don’t callLeaderboard()again in your code. But this is not always possible or straightforward. And you’d still need to make sure your documentation makes this really clear. And will your users read the documentation? Who knows. You may be opening the door to bugs.

Creating a Leaderboard • Second Attempt Using a Singleton Class

Now, you may be thinking: “Hey, I read about this great design pattern: the singleton pattern. Surely, this is a great time to use it…”.

The singleton is an important pattern in some languages. (Spoiler alert: it’s not so useful in Python – you’ll see why later). So let’s try to implement it in Python.

Let’s start with this question: “What’s the first thing that happens when you create an instance of a class in Python?”

If you answered: “Python calls its.__init__()method to initialise the object”, then you’re not quite right. Something else happens first. The.__init__()method initialises a “blank” instance of the class. But what creates that instance in the first place? It’s not.__init__().

It’s.__new__(). In most cases, you don’t need to worry about defining.__new__()when you define a class. The default behaviour is good enough. But in this case, you want to have more control over what happens when you create a new instance of theLeaderboardclass:

You add a few things to the class definition:

You add the class attribute._instance. This attribute belongs to the class, so each instance won’t have its own version, unlike the data attributes you create in.__init__(). The leading underscore in its name is a convention indicating that this attribute is meant for internal use only and that users of this class shouldn’t access it directly.

You define the special method.__new__(). Let’s explore the code in this method further.

When you don’t define a.__new__()special method, Python uses the default implementation, which is inherited from the base classobject. All Python classes inherit from theobjectclass. However, since you define a.__new__()method in your class, Python uses this method instead of the default.__new__().

But then you need to callsuper().__new__(), which creates the new blank object usingobject.__new__(). However, you only do this if the._instanceclass attribute isNone– that’s whatif cls._instance is None:does. Let’s understand what’s happening here.

The first time you create an instance of the class, you create the new blank instance since._instanceisNoneat first. You then assign this new instance to the class attribute._instance. The.__new__()method should return the instance, which is then passed on to.__init__(). But we’ll get there later.

What happens if you try to create a newLeaderboardobject again? The second time your code callsLeaderboard.__new__(), the class attribute._instanceis no longerNone. It now refers to an object of the class. Therefore, the code bypasses the creation of a new object and returns the existing one.

So, is the problem solved? Let’s find out. Here’s a reminder of the code used to explore this class (with a few extraprint()calls):

Here’s the output now:

Show leaderboard: Kate: 15 Stephen: 14 
 Show leaderboard: Sarah: 13 Max: 7 
 4344263552 4344263552 True

There’s good news and bad news – which one do you want first?

Let’s start with the good news. The variable namesleaderboardandanother_leaderboardrefer to the same object. Notice how the identity returned byid()is the same, and theisexpression returnsTrue. They’re the same object. When you callLeaderboard()the second time, your code doesn’t create a new instance. It uses the existing one.

Great.

But the leaderboards displayed are still different. Why?

You now have the same object – you’re not creating a new one. But you’re reinitialising it the second time you callLeaderboard(). When you call a class to create an instance by usingLeaderboard()(with parentheses), Python first calls the class’s.__new__(). But you dealt with that already – it doesn’t create a new instance. However, the instance returned by.__new__()is passed to the class’s.__init__().

And here’s a reminder of what yourLeaderboard.__init__()does:

Your instance already exists. It already has a.scoresdata attribute with some data in it. However, when you call.__init__()again, you overwrite.scoreswith an empty dictionary. Any data you already had is gone.

So, you now need to fix this, too. What a pain:

If you haven’t seen the built-inhasattr()function yet, it stands forhas attribute. You pass an object and the name of an attribute, and it returnsTrueorFalsedepending on whether that object has that attribute!

In this case, you passselfas the first argument. Andselfis the name that refers to the instance you’re dealing with. You also pass the string“initialised”as the second argument.

If it’s the first time.__init__()is called on this object, the object won’t have the attribute.initialisedsince it’s created in the.__init__()method itself. Therefore, the code within theifblock runs, creating.scoresand.initialised.

The second time you try to initialise the same object – and it will be the same object because of the code you wrote in.__new__()– the object will already have the.initialiseddata attribute. Therefore, the rest of the code in.__init__()won’t run.

You won’t overwrite.scoreswhen you callLeaderboard()a second time, or a third time… You’re ensuring that an object can only be initialised once.

Run the code now:

Show leaderboard: Kate: 15 Stephen: 14 
 Show leaderboard: Kate: 15 Stephen: 14 Sarah: 13 Max: 7 
 4336350080 4336350080 True

There’s still only oneLeaderboardobject. But now, you’re not overwriting any data it already has, either. As you can see, when you callanother_leaderboard = Leaderboard(), you don’t create a new instance. Instead, you reuse the one you already have. And you don’t erase its existing data, but add on to it.

Now, your class can only have one instance of theLeaderboardclass. It’s a singleton!

That’s a lot of work to create a useable singleton class.

And I won’t open the can of worms of the scenario where you may have multiple concurrent threads. Two or more threads may access.__new__()at the same time. And that’s not good!

The Python Coding Stack is getting bigger and better, and not just because there are more of you reading every week. I’ll send out an email announcing more articles, more goodies and more value soon. Stay tuned.

And make sure youupgrade to a paid subscriptionto make sure you don’t miss anything–now is a good time to upgrade, before monthly and yearly subscription prices go up (they only go up for new paid subscribers, never for existing ones)

Subscribe now

We’ve Learnt Stuff By Creating a Singleton

Creating a singleton class, as you did above, is a useful exercise to help you understand how Python creates and initialises new objects. From a learning perspective, it’s a great exercise.

But, do you need this in your code?

Generally, no.

It’s a lot of work.

There’s an easier way to achieve the same effect in Python (which may not exist in other programming languages).

And maybe you don’t really need a single global object that you refer to from all over your program.

Let’s explore some options. And no, I won’t cover all the options. I said I want to keep this article brief (but I’m already on 2k+ words). My aim here is to start you off on a journey to think about what goes where, when, and how…

Alternative to the Singleton Class • Move The Class to a New Module

Let’s roll back the changes to theLeaderboardclass. Delete the.__new__()method and the._instanceclass attribute. And revert back to the original, simpler.__init__(). However, place the class in its own script. Let’s call itleaderboard.py:

Note how.__new__()is no longer there and.__init__()simply creates the.scoresdata attribute.

There’s also one more line of code after the class definition – and only one. You create an instance of the class.

Now, let’s go back to your main script, which could be anywhere else in your program. Let’s call the main scriptmain.py:

The class defined inleaderboard.pyis not a singleton class. It’s a regular class. However, you create the one and only instance you’ll ever create withinleaderboard.py. Then, you simply import this instance usingfrom leaderboard import leaderboard. The variable name and module name don’t have to be the same, of course, but in this case they are.

Ah, what if you import the module more than once? I can hear you think… Python only loads a module once. Here’s a demonstration. Add the followingprint()call toleaderboard.py:

Now, go back tomain.pyand importleaderboarda second time anywhere else in your code:

Run this code. How many times do you see the textleaderboard.py loadedin your output?

leaderboard.py loaded Show leaderboard: Kate: 15 Stephen: 14 
 Show leaderboard: Kate: 15 Stephen: 14 Sarah: 13 Max: 7

Once. Modules are only loaded once. And the final output still shows the correct, combined leaderboard.

And there you go – you can only have one instance ofLeaderboardnow, without any of the hassle of messing around with.__new__()and.__init__().

Modules in Python give you a shortcut to create singleton-like behaviour.

Sometimes, You Can Simplify Further

In fact, do you really need theLeaderboardclass? Sometimes, you do, or you prefer to have everything relating to the leaderboard in a single object. In that case, the solutions in the previous section and in the one after this one are great.

But can you live with a simpler option?

Note that some functions’ names are updated to make them more readable since they’re no longer methods in a class.

Sure, this option may not always be possible. And some purists will scoff at these functions accessing and mutating a global variable (although you can modify the functions to accept the dictionary as one of the arguments, if you prefer).

The leading underscore in_scoresindicates that you don’t intend this variable to be used by the user. It’s not meant for public use. Users should only access it through the functions.

If you prefer, you can place._scoresand the functions in a separate module and import them. As you saw earlier, Python imports a module only once. Therefore, anything defined within a module is effectively a singleton! In Python, the behaviour of modules makes creating a single instance of a class to share across your code much easier – even trivial. Other languages don’t have this option, which is why the singleton pattern exists.

So, if you think a singleton class is the solution to your problem, consider whether this simpler option will do!

Need More Flexibility and Future-Proof Code?

Here’s yet another option. Create a file calledgame_services.py:

You can also define theLeaderboardclass within this module, if you prefer, but here I’m leaving it in its own module,leaderboard.py. TheGameServicesclass has a single data attribute,.leaderboard, which contains an instance ofLeaderboard. This instance ofLeaderboardis created when you create an instance ofGameServices, which you do in the final line in this script.

You’re using composition in this case. TheGameServicesclass has access toLeaderboardby having aLeaderboardinstance as one of its attributes. You can read more about composition in this pair of articles:Choose Your Fighter • Let’s Play (#1 in Inheritance vs Composition Pair)andChoose Your Fighter • Inheritance vs. Composition (#2 in Inheritance vs Composition Pair).

Back inmain.py, you can now import thisgame_servicesinstance:

At first sight, this version seems similar to the first alternative I presented above, just a bit more complex. However, instead of creating an instance ofLeaderboardthat is then used elsewhere, in this version, theLeaderboardinstance is included in a container, theGameServicesobject. You then use the instance of theGameServicesobject wherever needed.

There’s more boilerplate code, but you also get more flexibility with this version. What if you want to replace the leaderboard with a different one for testing purposes? The classic singleton class is hard to test. This option simplifies things because you can assign a newLeaderboardtogame_services.leaderboardor create a separateGameServicesobject for this purpose. Nothing else needs to change in your code.

You can also use a different implementation ofLeaderboard, say you have anAlternativeLeaderboardclass you want to experiment with. It’s easier and safer to make these changes when the leaderboard is included in theGameServicesobject.

And what if you later decide you want multiple leaderboards? Perhaps one for a version of the game and a different leaderboard for another version of the game? You no longer want a singleton! But with this version of the code, you can easily create another data attribute inGameServices. Sure, you’d be able to do so if usingLeaderboarddirectly, as in the first example. But this option makes it safer and easier to expand your code.

And perhaps, you have other services you want to share, not just a leaderboard. You can also add more data attributes.

Support The Python Coding Stack

Final Words

Note how the alternatives of the singleton class usestandardclasses that don’t need a.__new__()and extra work in the.__init__(), or they don’t use classes at all. They rely on composition within another class and on the fact that Python loads a module only once per program, so instances created in a module are effectively singletons when used elsewhere in the program.

There are other alternatives for situations where you may be tempted to use a singleton. And there may be some less common scenarios when the answer is still to create a singleton class.

So, I’m not stating that you absolutely never need to create a singleton class in Python. But in most cases, there are neater and more Pythonic alternatives.

Still, creating a singleton class, as we did earlier in this article, is a useful learning exercise!

This publication is entirely supported by its readers – there are no adverts, no sponsors! But it takes a lot of effort and time to get one of these articles out. If you want to support this publication further, and get exclusive articles, videos, and more goodies, you can become a paid subscriber.

Subscribe now

You can also support this publication by making aone-off contribution of any amount you wish.

Photo by Marek Piwnicki:https://www.pexels.com/photo/born-by-the-stars-17806401/

Code in this article uses Python 3.14

The code images used in this article are created usingSnappify.[Affiliate link]

You can also support this publication by making aone-off contribution of any amount you wish.

Support The Python Coding Stack

For more Python resources, you can also visitReal Python—you may even stumble on one of my own articles or courses there!

Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look atBreaking the Rules.

And you can find out more about me atstephengruppetta.com

Further reading related to this article’s topic:

The Key To The key Parameter in Python

Choose Your Fighter • Let’s Play (#1 in Inheritance vs Composition Pair)andChoose Your Fighter • Inheritance vs. Composition (#2 in Inheritance vs Composition Pair)

A Magical Tour Through Object-Oriented Programming in Python • Hogwarts School of Codecraft and Algorithmancy

Appendix: Code Blocks

Code Block #1
class Leaderboard: def __init__(self): self.scores = {}
Code Block #2
class Leaderboard: def __init__(self): self.scores = {} def add_score(self, player, score): if player in self.scores: self.scores[player] += score else: self.scores[player] = score def get_leaderboard(self): return sorted( self.scores.items(), key=lambda item: item[1], reverse=True, ) def display(self): for player, score in self.get_leaderboard(): print(f”{player}: {score}”) def reset(self): # You may want to add a confirmation step in a real application, # or save a backup to a file first self.scores.clear()
Code Block #3
# ... leaderboard = Leaderboard() leaderboard.add_score(”Stephen”, 10) leaderboard.add_score(”Kate”, 15) leaderboard.add_score(”Stephen”, 4) leaderboard.display()
Code Block #4
# ... # later in the game, or in another game instance... print(”\nNow Dealing With ‘another_leaderboard’”) another_leaderboard = Leaderboard() another_leaderboard.add_score(”Max”, 7) another_leaderboard.add_score(”Sarah”, 13) another_leaderboard.display()
Code Block #5
# ... print(id(leaderboard)) print(id(another_leaderboard)) print(leaderboard is another_leaderboard)
Code Block #6
class Leaderboard: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance # ...
Code Block #7
# ... leaderboard = Leaderboard() leaderboard.add_score(”Stephen”, 10) leaderboard.add_score(”Kate”, 15) leaderboard.add_score(”Stephen”, 4) print(”Show leaderboard:”) leaderboard.display() # later in the game, or in another game instance... another_leaderboard = Leaderboard() another_leaderboard.add_score(”Max”, 7) another_leaderboard.add_score(”Sarah”, 13) print(”\nShow leaderboard:”) another_leaderboard.display() print() print(id(leaderboard)) print(id(another_leaderboard)) print(leaderboard is another_leaderboard)
Code Block #8
class Leaderboard: # ... def __init__(self): self.scores = {} 		 # ...
Code Block #9
class Leaderboard: # ... def __init__(self): # Prevent reinitialisation if not hasattr(self, “initialised”): self.scores = {} self.initialised = True # ...
Code Block #10
# leaderboard.py class Leaderboard: def __init__(self): self.scores = {} def add_score(self, player, score): if player in self.scores: self.scores[player] += score else: self.scores[player] = score def get_leaderboard(self): return sorted( self.scores.items(), key=lambda item: item[1], reverse=True, ) def display(self): for player, score in self.get_leaderboard(): print(f”{player}: {score}”) def reset(self): # You may want to add a confirmation step in a real application, # or save a backup to a file first self.scores.clear() # Create a single instance of Leaderboard leaderboard = Leaderboard()
Code Block #11
# main.py from leaderboard import leaderboard leaderboard.add_score(”Stephen”, 10) leaderboard.add_score(”Kate”, 15) leaderboard.add_score(”Stephen”, 4) print(”Show leaderboard:”) leaderboard.display() # later in the game, or in another game instance... leaderboard.add_score(”Max”, 7) leaderboard.add_score(”Sarah”, 13) print(”\nShow leaderboard:”) leaderboard.display()
Code Block #12
# leaderboard.py print(”leaderboard.py loaded”) class Leaderboard: # ...
Code Block #13
# main.py from leaderboard import leaderboard leaderboard.add_score(”Stephen”, 10) leaderboard.add_score(”Kate”, 15) leaderboard.add_score(”Stephen”, 4) print(”Show leaderboard:”) leaderboard.display() # Note, we usually never place imports in the middle of a file, # but this is just to illustrate that the singleton instance # is shared even if we import it again. from leaderboard import leaderboard # later in the game, or in another game instance... leaderboard.add_score(”Max”, 7) leaderboard.add_score(”Sarah”, 13) print(”\nShow leaderboard:”) leaderboard.display()
Code Block #14
_scores = {} def add_score(player, score): if player in _scores: _scores[player] += score else: _scores[player] = score def get_leaderboard(): return sorted( _scores.items(), key=lambda item: item[1], reverse=True, ) def display_leaderboard(): for player, score in get_leaderboard(): print(f”{player}: {score}”) def reset_leaderboard(): # You may want to add a confirmation step in a real application, # or save a backup to a file first _scores.clear() add_score(”Stephen”, 10) add_score(”Kate”, 15) add_score(”Stephen”, 4) print(”Show leaderboard:”) display_leaderboard() # later in the game, or in another game instance... add_score(”Max”, 7) add_score(”Sarah”, 13) print(”\nShow leaderboard:”) display_leaderboard()
Code Block #15
# game_services.py from leaderboard import Leaderboard class GameServices: def __init__(self): self.leaderboard = Leaderboard() game_services = GameServices()
Code Block #16
# main.py from game_services import game_services game_services.leaderboard.add_score(”Stephen”, 10) game_services.leaderboard.add_score(”Kate”, 15) game_services.leaderboard.add_score(”Stephen”, 4) print(”Show leaderboard:”) game_services.leaderboard.display() # later in the game, or in another game instance... game_services.leaderboard.add_score(”Max”, 7) game_services.leaderboard.add_score(”Sarah”, 13) print(”\nShow leaderboard:”) game_services.leaderboard.display()

For more Python resources, you can also visitReal Python—you may even stumble on one of my own articles or courses there!

Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look atBreaking the Rules.

And you can find out more about me atstephengruppetta.com

https://www.thepythoncodingstack.com/p/creating-a-singleton-class-in-python