PythonObjectOriented
Singleton
import json
from threading import Lock
class SingletonMeta(type):
_instances = {}
_lock: Lock = Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DataManager(metaclass=SingletonMeta):
def __init__(self, filename):
self.filename = filename
self.data = None
self.index = None
self.load_data()
def load_data(self):
with open(self.filename, 'r') as file:
self.data = json.load(file)
self.index_data()
def index_data(self):
# Implement your indexing logic here.
# For example, if your data is a list of dicts:
self.index = {d['key']: d for d in self.data}
def get_data_by_key(self, key):
return self.index.get(key)
# Usage:
data_manager = DataManager('data.json')
print(data_manager.get_data_by_key('some_key'))
Explanation
Yes, the SingletonMeta metaclass along with the __call__ method is indeed the "secret sauce" that ensures __init__ is only called once for the class DataManager.
Here's a breakdown of how it works:
- Metaclass: A metaclass in Python is a class of a class that defines how a class behaves. A class is an instance of its metaclass.
- SingletonMeta __call__ method: The __call__ method in the metaclass is called when an instance of the class is created, i.e., when you call DataManager(). Normally, this would create a new instance each time; however, the metaclass modifies this behavior.
- Instance Checking: Inside the __call__ method, there's a check to see if an instance of the class already exists in the _instances dictionary.
- Lock: The Lock is used to make the check-and-create operation thread-safe, preventing two threads from creating an instance at the same time.
- Instance Creation and Caching: If the instance does not exist, it is created and stored in _instances[cls]. If it does exist, the existing instance is returned.
So, the flow is as follows:
- You call DataManager('data.json').
- The metaclass __call__ method checks if DataManager has an instance in _instances.
- If not, it creates one and stores it.
- If it does, it returns the stored instance.
Because of this mechanism, no matter how many times you call DataManager('data.json'), you will always get back the same instance, ensuring that __init__ is only called the first time, making DataManager effectively a singleton.
Abstract Class
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# r = Shape() # TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter
rect = Rectangle(10, 20)
print(rect.area())
print(rect.perimeter())