Implementing the Singleton Pattern in Python: Multiple Approaches and Thread Safety
This article explains the Singleton design pattern in Python, covering various implementation methods—including modules, decorators, classic classes, __new__ method, and metaclasses—while demonstrating thread‑safety concerns and solutions with locking, and provides complete code examples for each approach.
The Singleton pattern ensures that a class has only one instance, which is useful for shared resources such as configuration objects.
Python offers several ways to implement a singleton:
Using a module (modules are naturally singletons because they are loaded only once).
Using a decorator that caches the created instance.
Using a class with a classmethod that creates and stores a single instance.
Using the __new__ method together with a lock to guarantee thread safety.
Using a metaclass that overrides __call__ to enforce a single instance.
Below are the key code examples for each approach.
Module approach
<code>class Singleton(object):
def foo(self):
pass
singleton = Singleton()
</code>Import the singleton elsewhere with:
<code>from mysingleton import singleton
</code>Decorator approach
<code>def Singleton(cls):
_instance = {}
def _singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
@Singleton
class A(object):
a = 1
def __init__(self, x=0):
self.x = x
a1 = A(2)
a2 = A(3)
</code>Classmethod approach
<code>class Singleton(object):
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
# usage
obj = Singleton.instance()
</code>When used in multithreaded environments, the simple classmethod version can create multiple instances. Adding a lock solves the problem:
<code>import threading, time
class Singleton(object):
_instance_lock = threading.Lock()
def __init__(self):
time.sleep(1)
@classmethod
def instance(cls, *args, **kwargs):
with cls._instance_lock:
if not hasattr(cls, "_instance"):
cls._instance = cls(*args, **kwargs)
return cls._instance
</code>Running multiple threads that call Singleton.instance() now prints the same object address, confirming thread‑safe singleton behavior.
__new__ method approach
<code>class Singleton(object):
_instance_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
with cls._instance_lock:
if not hasattr(cls, "_instance"):
cls._instance = object.__new__(cls)
return cls._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2) # same address
</code>Metaclass approach
<code>class SingletonType(type):
_instance_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
with cls._instance_lock:
if not hasattr(cls, "_instance"):
cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)
return cls._instance
class Foo(metaclass=SingletonType):
def __init__(self, name):
self.name = name
obj1 = Foo('name')
obj2 = Foo('name')
print(obj1, obj2) # same address
</code>All the above methods achieve the same goal: only one instance of the class exists throughout the program, with the metaclass and __new__ approaches offering the most transparent usage (i.e., obj = Foo() works as usual).
In addition to the technical explanations, the article also includes promotional sections encouraging readers to follow the public account and scan QR codes for free Python learning resources.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.