Menu
Coddy logo textTech

Singleton Pattern

Part of the Object Oriented Programming section of Coddy's Python journey — lesson 45 of 64.

The Singleton pattern ensures a class has only one instance and provides a global point of access to it. This is useful for resources like database connections or configuration settings.

Here is a basic Singleton implementation:

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

The __new__ method controls object creation. It checks if an instance already exists before creating a new one.

Create two instances of the Singleton class:

singleton1 = Singleton()
singleton2 = Singleton()

Check if both variables reference the same object:

print(singleton1 is singleton2)  # True
print(id(singleton1))            # Same memory address
print(id(singleton2))            # Same memory address

Here is a more practical example with a database connection:

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to MySQL database"
            print("Creating new database connection")
        return cls._instance
    
    def query(self, sql):
        return f"Executing: {sql}"

# First access creates the connection
db1 = DatabaseConnection()
print(db1.connection)

# Second access reuses the same connection
db2 = DatabaseConnection()
print(db2.connection)

print(db1.query("SELECT * FROM users"))
print(db1 is db2)

Create a configuration manager using Singleton:

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {}
        return cls._instance
    
    def set_setting(self, key, value):
        self.settings[key] = value
    
    def get_setting(self, key):
        return self.settings.get(key)

config1 = Config()
config1.set_setting("debug", True)

config2 = Config()
print(config2.get_setting("debug"))  # True - same settings

Output:

True
140234567890123
140234567890123
Creating new database connection
Connected to MySQL database
Connected to MySQL database
Executing: SELECT * FROM users
True
True

Key Point: The Singleton pattern uses __new__ to control object creation, ensuring only one instance exists. Use it for resources that should have only one copy throughout your application, like database connections, loggers, or configuration managers. Remember that all variables pointing to a Singleton reference the same object in memory.

challenge icon

Challenge

Easy

In this challenge, you'll implement the Singleton design pattern for a database connection class.

  • Edit databaseconnection.py to implement the Singleton pattern following the TODO comments
  • The driver.py file contains extensive test scenarios and should not be modified

Cheat sheet

The Singleton pattern ensures a class has only one instance and provides a global point of access to it.

Basic Singleton implementation using __new__:

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

The __new__ method controls object creation by checking if an instance already exists before creating a new one.

Testing Singleton instances:

singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True - same object

Practical example with database connection:

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to MySQL database"
        return cls._instance
    
    def query(self, sql):
        return f"Executing: {sql}"

Configuration manager example:

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {}
        return cls._instance
    
    def set_setting(self, key, value):
        self.settings[key] = value
    
    def get_setting(self, key):
        return self.settings.get(key)

Use Singleton for resources that should have only one copy throughout your application, like database connections, loggers, or configuration managers.

Try it yourself

from databaseconnection import DatabaseConnection

# Comprehensive test case handler
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # Should print True

elif test_case == "connect_state":
    db = DatabaseConnection()
    print(f"Initial connected state: {db.connected}")  # Should print False
    db.connect()
    print(f"After connect: {db.connected}")  # Should print True

elif test_case == "disconnect_state":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()
    print(f"After disconnect: {db.connected}")  # Should print False

elif test_case == "multiple_instances_same_state":
    db1 = DatabaseConnection()
    db1.connect()
    db2 = DatabaseConnection()
    print(f"db2 connected state: {db2.connected}")  # Should print True

elif test_case == "host_value":
    db = DatabaseConnection()
    print(f"Default host: {db.host}")  # Should print "localhost"
    db.host = "new-server"
    db2 = DatabaseConnection()
    print(f"New instance host: {db2.host}")  # Should print "new-server"

elif test_case == "init_once":
    db1 = DatabaseConnection()
    db1.host = "custom-host"
    db2 = DatabaseConnection()
    print(f"Both instances have same host: {db1.host == db2.host}")  # Should print True
    print(f"Host value: {db1.host}")  # Should print "custom-host"

elif test_case == "connect_message":
    db = DatabaseConnection()
    db.connect()  # Should print "Connected to database at localhost"

elif test_case == "disconnect_message":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()  # Should print "Disconnected from database"

elif test_case == "attribute_modification":
    db1 = DatabaseConnection()
    db1.port = 3306
    db2 = DatabaseConnection()
    print(f"db2 has port attribute: {hasattr(db2, 'port')}")  # Should print True
    print(f"db2 port value: {db2.port}")  # Should print 3306

elif test_case == "reset_connection":
    db1 = DatabaseConnection()
    db1.connect()
    print(f"Connected state: {db1.connected}")  # Should print True
    db2 = DatabaseConnection()
    print(f"New instance connected state: {db2.connected}")  # Should print True
    db2.disconnect()
    db3 = DatabaseConnection()
    print(f"After disconnect, new instance state: {db3.connected}")  # Should print False

elif test_case == "multiple_connects":
    db = DatabaseConnection()
    db.connect()
    db.connect()
    db.connect()
    print(f"Connected state after multiple connects: {db.connected}")  # Should print True
    db.disconnect()
    print(f"Connected state after disconnect: {db.connected}")  # Should print False

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # Will still print "Connected to database at localhost"
    # The connect method has a hardcoded message that doesn't use the host attribute
    print("Message still shows localhost: True")
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming