PythonObjectOriented

From Traxel Wiki
Jump to navigation Jump to search

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:

  1. 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.
  2. 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.
  3. Instance Checking: Inside the __call__ method, there's a check to see if an instance of the class already exists in the _instances dictionary.
  4. 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.
  5. 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:

  1. You call DataManager('data.json').
  2. The metaclass __call__ method checks if DataManager has an instance in _instances.
  3. If not, it creates one and stores it.
  4. 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())