How can you make a singleton in Python?
A singleton class is a class that only allows creating one instance of itself.
None
is an example of a singleton object in Python.
The class of None
is NoneType
:
>>> type(None)
<class 'NoneType'>
If we try to call the NoneType
class, we'll get back an instance of that class.
>>> new_none = type(None)()
But every instance of the NoneType
class is identical to every other instance of the NoneType
class:
>>> new_none is None
True
So there's only one instance of NoneType
in Python, and it's None
.
Here we have a class called EventTracker
that helps us keep track of events in our application:
class EventTracker:
def __init__(self):
self.api_url = "http://example.com"
def track(self):
print(f"TODO track event at {self.api_url}")
We'd like to share just one instance of this class for our whole Python process. To do that, we could make a singleton class which only allows one instance of itself to be created.
We can make a singleton class by customizing our class to have a custom constructor method:
class EventTracker:
_self = None
def __new__(cls):
if cls._self is None:
cls._self = super().__new__(cls)
return cls._self
def __init__(self):
self.api_url = "http://example.com"
def track(self):
print(f"TODO track event at {self.api_url}")
Python's __init__
method is called the initializer method.
By the time the __init__
method gets called, our class instance has already been constructed.
That's what the self
argument in a method is:
def __init__(self):
...
The __new__
method is the constructor method.
Its job is to do the actual work of constructing and then returning a new instance of our class:
class EventTracker:
_self = None
def __new__(cls):
if cls._self is None:
cls._self = super().__new__(cls)
return cls._self
...
In this EventTracker
class, our constructor method case always returns the same instance of our class.
If it doesn't have a class instance yet, it calls its parent class's constructor (that's object.__new__
since object
is the parent class of all Python classes by default):
if cls._self is None:
cls._self = super().__new__(cls)
If we do already have a class instance, we just return it:
return cls._self
So every "instance" of our class is actually the same instance.
Meaning when we first call our class, we'll get back a new instance:
>>> tracker = EventTracker()
>>> tracker
<__main__.EventTracker object at 0x7f32bdd780a0>
But if we call our class a second time, we'll get back the same exact instance:
>>> tracker2 = EventTracker()
>>> tracker2
<__main__.EventTracker object at 0x7f32bdd780a0>
>>> tracker2 is tracker
True
Every instance of our class is the same, which means we've made a singleton class.
Creating a singleton class by making a custom constructor does work, but it might seem a little bit misleading to someone using this class. It's counter-intuitive that every time we call our class we'll always get back the same class instance.
The singleton pattern usually doesn't make sense in Python in its purest form. Instead, it usually makes more sense to make a single instance of a class, and then assign that instance to a global variable in our module.
Here we've renamed our class to _EventTracker
(putting an _
prefix just before it to denote "private by convention"):
class _EventTracker:
def __init__(self):
self.api_url = "http://example.com"
def track(self):
print(f"TODO track event at {self.api_url}")
tracker = _EventTracker()
It isn't strictly necessary to rename your class, but it does let people know that you're not actually meant to use the class directly. You're only ever meant to interact with this one instance.
When using our class, we won't actually interact with the class directly. Instead we would import and use just the single class instance we've made:
>>> from events import tracker
>>> tracker.track()
TODO track event at http://example.com
If there's never any reason to have two instances of your class, you might consider not making a class at all and instead just use the global state within your module.
Here we've made an events
module that does the same thing as our events
module before:
api_url = "http://example.com"
def track():
print(f"TODO track event at {api_url}")
But we don't have a class. Instead, we've taken the state and the functionality that was within that class, and we've put it at the module level.
To use this module, instead of saying events.tracker.track()
like we would've done before, we can just use events.track()
:
>>> import events
>>> events.track()
TODO track event at http://example.com
We're now calling our track
function at the module level.
Now, this works for the same reason that making global object works: modules in Python act like singletons!
Classes are handy, but you don't always need a class in Python.
When you import a module, Python will run the code within that module and create a new module object:
>>> events
<module 'events' from '/home/trey/events.py'>
But if we import the same module again, Python will give us back the exact same module object:
>>> import events as again
>>> again
<module 'events' from '/home/trey/events.py'>
>>> again is events
True
So if we change some of the global state within one of these module objects:
>>> again.api_url
'http://example.com'
>>> again.api_url = "NEW URL"
That change would be reflected in the other object as well (because those two variables point to the same object):
>>> events.api_url
'NEW URL'
For each Python module, there is only ever one module object.
So if you need to share global state in Python, it's often simplest to just use the global variables within a module.
The classic singleton design pattern doesn't always make a lot of sense in Python.
Singletons are most often used for sharing global state. In Python, if you need to share some global state, you can often just use a module object because in Python modules are singletons.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Classes are a way to bundle functionality and state together.
The terms "type" and "class" are interchangeable: list
, dict
, tuple
, int
, str
, set
, and bool
are all classes.
You'll certainly use quite a few classes in Python (remember types are classes) but you may not need to create your own often.
To track your progress on this Python Morsels topic trail, sign in or sign up.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.