# 23. Singleton Pattern

The Singleton Pattern ensures that a class has only one instance throughout the application's lifetime and provides a global point of access to that instance.

**1. Classic Singleton using a Private Class Variable**

```python
class Singleton:
    _instance = None

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

obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2)  # True
```

***

**2. Singleton with Initialization**

```python
class Singleton:
    _instance = None

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

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

obj1 = Singleton(10)
obj2 = Singleton(20)
print(obj1 is obj2)  # True
print(obj1.value)    # 10
print(obj2.value)    # 10
```

***

**3. Singleton Using a Decorator**

```python
def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class MyClass:
    def __init__(self, name):
        self.name = name

obj1 = MyClass("Object1")
obj2 = MyClass("Object2")
print(obj1 is obj2)  # True
print(obj1.name)     # Object1
print(obj2.name)     # Object1
```

***

**4. Singleton Using a Metaclass**

```python
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 MyClass(metaclass=SingletonMeta):
    def __init__(self, value):
        self.value = value

obj1 = MyClass(42)
obj2 = MyClass(99)
print(obj1 is obj2)  # True
print(obj1.value)    # 42
print(obj2.value)    # 42
```

***

**5. Thread-Safe Singleton**

```python
import threading

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

    def __new__(cls, *args, **kwargs):
        with cls._lock:
            if not cls._instance:
                cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2)  # True
```

***

**6. Singleton with Global Instance**

```python
class Singleton:
    def __init__(self, name):
        self.name = name

_instance = None

def get_instance(name=None):
    global _instance
    if _instance is None:
        _instance = Singleton(name)
    return _instance

obj1 = get_instance("First")
obj2 = get_instance("Second")
print(obj1 is obj2)  # True
print(obj1.name)     # First
print(obj2.name)     # First
```

***

**7. Singleton Using a Module**

```python
# singleton_module.py
class Singleton:
    def __init__(self, value):
        self.value = value

singleton = Singleton(42)

# main.py
from singleton_module import singleton

print(singleton.value)  # 42
```

***

**8. Singleton with Weak References**

```python
import weakref

class Singleton:
    _instances = weakref.WeakValueDictionary()

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

obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2)  # True
```

***

**9. Singleton Using Borg Pattern (Shared State)**

```python
class Borg:
    _shared_state = {}

    def __init__(self):
        self.__dict__ = self._shared_state

class Singleton(Borg):
    def __init__(self, value=None):
        super().__init__()
        if value:
            self.value = value

obj1 = Singleton("First")
obj2 = Singleton("Second")
print(obj1 is obj2)  # False (different objects)
print(obj1.value)    # Second
print(obj2.value)    # Second
```

***

**10. Config Manager Singleton Example**

```python
class ConfigManager:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(ConfigManager, cls).__new__(cls)
            cls._instance._config = {}
        return cls._instance

    def set_config(self, key, value):
        self._config[key] = value

    def get_config(self, key):
        return self._config.get(key)

config1 = ConfigManager()
config2 = ConfigManager()
config1.set_config("database", "MySQL")
print(config1 is config2)                # True
print(config2.get_config("database"))   # MySQL
```

***

These examples demonstrate various ways to implement the Singleton Pattern in Python, providing flexibility for different use cases such as thread safety, module-level singletons, and shared state.
