Singleton
'''
Imagine you're on a plane, there is a control tower which decides which plane goes on which airway.
There are multiple planes, but only 1 control tower. If they had multiple control tower and let's say Flight A is on Runway1 which is allotted by ControlTower1
and ControlTower2 allots Flight B also on Runway1 (because it doesn't know about ControlTower1) then planes will crash.
It's harder to manage multiple ControlTowers, and hence a singleton pattern (1 Centralised Control Tower to decide which plane goes where) is what we need here to solve this problem.
Singleton: You declare a private instance and if it hasn't been created you create else you return the already created instance of the class.
'''
import sys
# without Singleon (Plane Crash)
class ControlTower:
def __init__(self):
print("Iniatilising Control Tower")
Tower1 = ControlTower()
Tower2 = ControlTower()
print("Singleton memory:", sys.getsizeof(Tower1.__dict__), "bytes") # 288 bytes, we are saving 288 bytes each time we call this because of singleton
if Tower1 is Tower2:
print("Planes will not crash since they are handled by a centralised Control Tower")
else:
print("Planes will crash")
print("*" * 60)
# Singleton pattern
class ControlTowerSingleton:
_instance = None # pvt variable
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
print("Iniatilising Control Tower")
return cls._instance
Tower1_singleton = ControlTowerSingleton()
Tower2_singleton = ControlTowerSingleton()
if Tower1_singleton is Tower2_singleton:
print("Planes will not crash since they are handled by a centralised Control Tower")
else:
print("Planes will crash")
print("\n")
print("*" * 60)
# More examples for practice.
# DB making a connection repetitive amounts of time, better to make the connection once and then do whatever SQL commands you want. Saves a lot of memory
'''
Without Singleton: Each DatabaseConnection() would create a new connection, wasting memory and time.
With Singleton: First call creates the connection. Later calls just reuse it.
'''
import sqlite3 as sql
class DBConnection:
_instance = None
def __new__(cls):
if not cls._instance:
print("Creating new DB connection")
cls._instance = super().__new__(cls)
else:
print("Reusing existing DB connection")
return cls._instance
def query(self, sql):
print(f"Executing query: {sql}")
db1 = DBConnection()
db2 = DBConnection()
if db1 is db2:
print("Singleton Pattern is being used here")
db1.query("SELECT * FROM users;")
db2.query("INSERT INTO logs VALUES ('test');")
print("\n")
print("*" * 60)
# Logging, declare it once so you don't have to open and create new file for logging each time you want to.
'''
Without Singleton: If you create new loggers each time, each might open the file separately → performance hit + duplicated logs.
With Singleton: First logger sets up the file handler. Every other call just reuses the same instance.
'''
import logging
class Logger:
_instance = None
def __new__(cls):
if not cls._instance:
print("Creating new logger")
cls._instance = super().__new__(cls)
cls._instance.logger = logging.getLogger(__name__)
cls._instance.logger.setLevel(logging.INFO)
else:
print("Reusing existing logger")
return cls._instance
def log(self, message):
self.logger.info(message)
print(message)
logger1 = Logger()
logger2 = Logger()
if logger1 is logger2:
print("Singleton Pattern is being used here")
else:
print("Singleton pattern not being used")