How to create a singleton class in Python

Learn to create a singleton class in Python. Explore different methods, tips, real-world applications, and how to debug common errors.

How to create a singleton class in Python
Published on: 
Mon
Apr 6, 2026
Updated on: 
Wed
Apr 8, 2026
The Replit Team

A singleton class ensures only one instance of that class ever exists. This design pattern is crucial to manage shared resources, like database connections or global configuration settings, across your application.

You'll explore several techniques to implement singletons in Python. You'll find practical tips, see real-world applications, and get debugging advice so you can confidently apply this powerful design pattern.

Basic implementation with class variable

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

s1 = Singleton()
s2 = Singleton()
print(s1 is s2)--OUTPUT--TRUE

The key to this implementation is overriding the __new__ method, which controls object creation. You're essentially intercepting the instantiation process before it happens. The class variable _instance acts as a cache for the single object.

The first time you call Singleton(), _instance is None, so a new object is created and stored. Every time after that, the if condition fails, and the method returns the already-existing object stored in _instance. This ensures that no matter how many times you try to instantiate the class, you always get back the very first object created.

Common implementation techniques

Building on the basic __new__ implementation, you can also create singletons with decorators, add initialization checks, or leverage the underlying power of metaclasses.

Using a decorator for singleton creation

def singleton(cls):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return get_instance

@singleton
class MyClass:
pass

a = MyClass()
b = MyClass()
print(a is b)--OUTPUT--TRUE

This approach uses a decorator to wrap your class, making the singleton logic reusable and clean. The @singleton decorator replaces your class with an inner function, get_instance, which handles the object creation process.

  • A dictionary named instances acts as a cache for any created objects.
  • The first time you call MyClass(), a new instance is created and stored in the instances dictionary.
  • Every subsequent call simply retrieves the existing instance from the dictionary, guaranteeing only one object is ever made.

Using __new__ method with initialization check

class Singleton:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance

def __init__(self, value=None):
if not self._initialized:
self.value = value
self._initialized = True

s1 = Singleton("first")
s2 = Singleton("second")
print(s1.value, s2.value, s1 is s2)--OUTPUT--first first True

A tricky part of singletons is that __init__ gets called every time you try to create an instance, which can overwrite your object's initial state. This implementation solves that problem with a simple flag.

  • An _initialized flag is set on the instance only when it's first created inside __new__.
  • The __init__ method checks this flag before running. It only executes its logic once.
  • This prevents re-initialization, which is why s2 = Singleton("second") doesn't change the value to "second". The original instance from s1 is preserved.

Using metaclasses for singleton pattern

class SingletonMeta(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]

class MySingleton(metaclass=SingletonMeta):
pass

instance1 = MySingleton()
instance2 = MySingleton()
print(instance1 is instance2)--OUTPUT--TRUE

This is a more advanced approach that uses a metaclass to control how the class itself behaves. The metaclass, SingletonMeta, intercepts the class instantiation process by defining a custom __call__ method. This method is what runs whenever you try to create an object, like with MySingleton().

  • A dictionary named _instances holds the single object for each class using this metaclass.
  • The first time you call the class, it’s not in the dictionary, so a new instance is created and stored.
  • Every call after that simply returns the already-existing instance from the dictionary.

Advanced singleton patterns

While the common patterns work for many cases, you'll often need more robust solutions for handling concurrency, on-demand creation, and dynamic configurations.

Thread-safe singleton implementation

import threading

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

s1 = Singleton()
s2 = Singleton()
print(s1 is s2)--OUTPUT--TRUE

When multiple threads run at once, you risk a race condition where each thread creates its own instance, breaking the singleton pattern. This implementation solves that problem with a threading.Lock, which is crucial for concurrent applications.

  • The with cls._lock: statement acts as a gatekeeper, ensuring only one thread can execute the creation logic at a time.
  • The first thread to acquire the lock checks if cls._instance is None, creates the object, and then releases the lock.
  • Any other threads must wait. By the time they get the lock, the instance already exists, so they just get the existing object.

Lazy initialization with get_instance() method

class LazySingleton:
__instance = None

@classmethod
def get_instance(cls):
if not cls.__instance:
cls.__instance = LazySingleton()
return cls.__instance

singleton1 = LazySingleton.get_instance()
singleton2 = LazySingleton.get_instance()
print(singleton1 is singleton2)--OUTPUT--TRUE

This pattern is 'lazy' because it delays creating the object until you actually need it. You don't call the constructor directly. Instead, you use the get_instance() class method to fetch the object.

  • The first time you call get_instance(), it sees that the private __instance variable is empty and creates the object.
  • On every following call, it finds the existing object and simply returns that, ensuring you always get the same one.

Singleton with configuration parameters

class ConfigurableSingleton:
_instance = None

def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.configure(*args, **kwargs)
return cls._instance

def configure(self, config_value=None):
self.config = config_value

s = ConfigurableSingleton("initial config")
print(s.config)
s2 = ConfigurableSingleton("new config")
print(s.config, s2.config)--OUTPUT--initial config
initial config initial config

This pattern lets you set up your singleton with initial parameters, but only the first time it's created. A separate configure method handles the setup, and it's only called once from within the __new__ method.

  • The configuration logic is placed inside the if cls._instance is None: block, guaranteeing it runs only during the very first instantiation.
  • Because of this, any later attempts to create an instance, like with ConfigurableSingleton("new config"), simply return the existing object. The original configuration is locked in and won't be overwritten.

Move faster with Replit

Replit is an AI-powered development platform with all Python dependencies pre-installed, so you can skip setup and start coding instantly. You can go from understanding a concept, like the singleton pattern, to applying it without wrestling with environment configurations.

Instead of piecing together individual techniques, you can use Agent 4 to build complete, working applications. It handles the entire development lifecycle—from writing code and connecting to databases to managing APIs and deploying your project—all from a simple description of what you want to build.

  • A global configuration manager that ensures every part of your application uses the same API keys and settings.
  • A centralized logging service that collects messages from different modules into a single, organized stream.
  • A database connection handler that manages a shared pool of connections, improving your app's performance and resource use.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

While powerful, singletons introduce unique challenges with inheritance, serialization, and copying that can unexpectedly break the one-instance rule.

Inheritance can cause trouble if a subclass and its parent singleton class end up sharing the same instance. For example, if DatabaseConnection is a singleton and you create a subclass called PostgresConnection, you expect an instance of PostgresConnection, not the parent class. A basic singleton implementation might incorrectly return the parent instance for both.

  • The solution is to make your singleton mechanism class-aware.
  • Instead of a single _instance variable, use a dictionary that maps each class to its own unique instance.
  • This ensures that when you instantiate a subclass, it gets its own singleton instance, separate from the parent's. The metaclass and decorator patterns often handle this correctly.

Serializing a singleton with a tool like pickle and then deserializing it can accidentally create a new instance, defeating the purpose of the pattern. This happens because unpickling typically bypasses the class's custom __new__ or __call__ logic that enforces the singleton behavior.

  • To fix this, you can tell pickle how to find the existing instance instead of creating a new one.
  • You do this by implementing the __reduce__ method in your singleton class.
  • This method should return a callable—like a static get_instance() method—that pickle will use during deserialization to retrieve the one true instance.

Similarly, using copy.deepcopy() on a singleton instance will create a duplicate, breaking the pattern. The function is designed to create a completely new and independent copy of an object, which is the exact opposite of what you want with a singleton.

  • You can prevent this by overriding the __deepcopy__ method.
  • Inside your custom __deepcopy__ method, you simply ignore the request to create a new object and instead return self.
  • This effectively intercepts the copy operation and ensures that any attempt to deep-copy the singleton just returns a reference to the existing instance.

Handling inheritance in singleton classes

Inheritance can trip up a simple singleton. When a child class extends a singleton parent, it can unexpectedly create a new, separate instance instead of returning the existing one. As the following code shows, this means parent is child will be false.

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

class ChildSingleton(Singleton):
pass

parent = Singleton()
child = ChildSingleton()
print(parent is child) # Will print False

The problem is that the cls._instance assignment creates a separate attribute for the child class. ChildSingleton doesn't see the parent's existing object and makes its own, resulting in two different instances. The following code shows how to fix this.

class Singleton:
_instances = {}

def __new__(cls):
if cls not in cls._instances:
cls._instances[cls] = super().__new__(cls)
return cls._instances[cls]

class ChildSingleton(Singleton):
pass

parent = Singleton()
child = ChildSingleton()
print(parent is child) # Still False, but each class has its own singleton

This solution uses a dictionary, _instances, to give each class its own singleton. Now, Singleton and ChildSingleton each manage a unique instance, which is why parent is child remains False. This ensures that each subclass behaves as a proper singleton without interfering with its parent or siblings. You'll want this pattern whenever you're creating a hierarchy of classes that all need to follow the singleton design pattern independently.

Fixing serialization issues with pickle

Serializing a singleton with Python's pickle module can unexpectedly break the one-instance rule. When you deserialize the object, pickle bypasses your singleton's creation logic and simply makes a new instance. The following code shows exactly how this happens, resulting in two separate objects.

import pickle

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

s1 = Singleton()
serialized = pickle.dumps(s1)
s2 = pickle.loads(serialized)
print(s1 is s2) # Will print False - singleton pattern broken

The pickle.loads() function reconstructs the object from its byte stream, bypassing the __new__ method entirely. This creates a fresh instance instead of returning the existing one. The following code shows how to fix this.

import pickle

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __reduce__(self):
return (self.__class__, ())

s1 = Singleton()
serialized = pickle.dumps(s1)
s2 = pickle.loads(serialized)
print(s1 is s2) # Now prints True

The fix is to implement the __reduce__ method, which tells pickle how to handle deserialization. By returning (self.__class__, ()), you're instructing pickle to call the class constructor again. This call is intercepted by your __new__ method, which correctly returns the existing instance instead of creating a new one. This simple override ensures that even after being "pickled" and "unpickled," you're still working with the one true singleton object.

Preventing singleton pattern breaking with copy.deepcopy()

Using copy.deepcopy() on a singleton is a classic pitfall because it's built to do the exact opposite of what you want—create a new, independent object. This action bypasses your singleton logic entirely. The following code shows this break in action.

import copy

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

original = Singleton()
copied = copy.deepcopy(original)
print(original is copied) # Will print False

The copy.deepcopy() function is meant to create a fresh copy, so it ignores the singleton's __new__ method entirely. This results in a second, unwanted instance. See how to override this behavior in the code below.

import copy

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __deepcopy__(self, memo):
return self

original = Singleton()
copied = copy.deepcopy(original)
print(original is copied) # Now prints True

To fix this, you override the __deepcopy__ method. Instead of letting it create a new object, your custom method simply returns self. This effectively tells Python to return the existing instance whenever a deep copy is requested. You'll need to watch out for this issue anytime your code or a library you're using might try to duplicate objects, as it can silently break your singleton's guarantee of a single instance.

Real-world applications

Now that you've navigated the technical challenges, you can apply singletons to manage shared resources like a central logger or database connection pool.

Using Logger singleton for application logging

This pattern ensures that no matter how many Logger objects you instantiate, they all point to the same instance, consolidating messages from across your application.

class Logger:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.log_history = []
return cls._instance

def log(self, message):
self.log_history.append(message)
print(f"LOG: {message}")

logger1 = Logger()
logger2 = Logger()
logger1.log("User login successful")
logger2.log("Database query executed")
print(logger1.log_history)

The __new__ method intercepts object creation, ensuring only one Logger instance ever exists. On the first call, it creates the object and attaches a log_history list. Any subsequent call to Logger() doesn't create a new object; it just returns the original one.

  • This is why logger1 and logger2 are the same. When you call the log method on either variable, you are modifying the same shared list. The final printout confirms both messages were captured in one place.

Implementing a DBConnectionManager for database access

A DBConnectionManager singleton guarantees your application uses one and only one database connection, preventing resource exhaustion and improving performance.

class DBConnectionManager:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connection = "Database connection established"
return cls._instance

def execute_query(self, query):
return f"Executing: {query} on {self.connection}"

db1 = DBConnectionManager()
db2 = DBConnectionManager()
print(db1.execute_query("SELECT * FROM users"))
print(db1 is db2)

The __new__ method intercepts object creation. On the first call to DBConnectionManager(), it creates the instance and sets the connection attribute. Any subsequent call bypasses this creation logic and just returns that original object.

  • This is why db1 and db2 are identical—they both point to the same object in memory.
  • As a result, when you call execute_query on either variable, you're using the exact same instance and its single connection attribute, which is confirmed by the output.

Get started with Replit

Turn your knowledge into a real tool. Describe what you want to build to Replit Agent, like “a centralized logger for my app” or “a settings manager that uses a single configuration.”

Replit Agent will write the code, test for errors, and deploy your application for you. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.