Source code for backtrader.mixins.singleton
#!/usr/bin/env python
"""Singleton Mixin Module - Singleton pattern implementation.
This module provides the SingletonMixin class for implementing the
singleton pattern without using metaclasses. This is part of the
metaprogramming removal effort in backtrader.
Classes:
SingletonMixin: Mixin class that implements the singleton pattern.
ParameterizedSingletonMixin: Singleton mixin with parameter support.
StoreBase: Base class for store singletons.
Example:
Creating a singleton class:
>>> class MyClass(SingletonMixin):
... pass
>>> a = MyClass()
>>> b = MyClass()
>>> assert a is b # True - same instance
"""
import threading
import weakref
from typing import Any, Dict, Optional
[docs]
class SingletonMixin:
"""Mixin class to implement singleton pattern using __new__ method.
This replaces the MetaSingleton metaclass pattern, providing the same
functionality without using metaclasses. Each class that inherits from
this mixin will maintain exactly one instance per class.
Thread-safe implementation using threading.Lock.
Features:
- Thread-safe singleton creation
- Per-class singleton instances (subclasses get their own instances)
- Automatic cleanup when instances are garbage collected
- Maintains compatibility with existing parameter systems
"""
# Class-level storage for singleton instances
# Using WeakValueDictionary for automatic cleanup
_instances: Dict[type, Any] = weakref.WeakValueDictionary()
_lock = threading.Lock()
[docs]
def __new__(cls, *args, **kwargs):
"""Create or return existing singleton instance for the class."""
# Check if instance already exists (fast path, no lock)
if cls in cls._instances:
return cls._instances[cls]
# Acquire lock for thread-safe creation
with cls._lock:
# Double-check pattern: instance might have been created
# by another thread while waiting for lock
if cls in cls._instances:
return cls._instances[cls]
# Create new instance using object.__new__ directly
# This avoids recursion issues with mixin inheritance
instance = object.__new__(cls)
# Store instance before calling __init__ to handle potential
# recursive calls during initialization
cls._instances[cls] = instance
return instance
@classmethod
def _reset_instance(cls):
"""Reset the singleton instance for this class.
This is mainly for testing purposes and should be used with caution
in production code.
"""
with cls._lock:
if cls in cls._instances:
del cls._instances[cls]
@classmethod
def _get_instance(cls) -> Optional[Any]:
"""Get the current singleton instance if it exists.
Returns:
The singleton instance if it exists, None otherwise.
"""
return cls._instances.get(cls)
@classmethod
def _has_instance(cls) -> bool:
"""Check if a singleton instance exists for this class.
Returns:
True if instance exists, False otherwise.
"""
return cls in cls._instances
[docs]
class ParameterizedSingletonMixin(SingletonMixin):
"""Enhanced singleton mixin that integrates with parameter systems.
This version is specifically designed to work with backtrader's
parameter system, maintaining compatibility with MetaParams functionality.
"""
[docs]
def __init__(self, *args, **kwargs):
"""Initialize singleton instance only once."""
if getattr(self, "_singleton_initialized", False):
return
self._singleton_initialized = True
# Backward compatibility alias
StoreBase = ParameterizedSingletonMixin