Source code for pyexperiment.utils.Singleton

"""Provides data structures for unique objects.

The `Singleton` class whose implementation is inspired by Tornado's
singleton (tornado.ioloop.IOLoop.instance()), can be inherited by
classes which only need to be instantiated once (for example a global
settings class such as :class:`pyexperiment.Config`). This design
pattern is often criticized, but it is hard to beat in terms of
simplicity.

A variant of the `Singleton`, the `DefaultSingleton` provides an
abstract base class for classes that are only instantiated once, but
need to provide an instance before being properly initialized (such as
`pyexperiment.Logger.Logger`).

The function `delegate_singleton` 'thunks' a `Singleton` so that calls
to the singleton instance's methods don't need to be ugly chain calls.

Written by Peter Duerr (Singleton inspired by Tornado's
implementation)
"""
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import

import threading
import inspect


[docs]class Singleton(object): """Singleton base-class (or mixin) """ __singleton_lock = threading.Lock() """Lock to prevent conflicts on the singleton instance """ __singleton_instance = None """The singleton instance """ @classmethod
[docs] def get_instance(cls): """Get the singleton instance """ if cls.__singleton_instance is None: with cls.__singleton_lock: if cls.__singleton_instance is None: cls.__singleton_instance = cls() return cls.__singleton_instance
@classmethod
[docs] def reset_instance(cls): """Reset the singleton """ if cls.__singleton_instance is not None: with cls.__singleton_lock: if cls.__singleton_instance is not None: cls.__singleton_instance = None
[docs]class DefaultSingleton(Singleton): """ABC for singleton that does not automatically initialize If get_instance is called on an uninitialized DefaultSingleton, a pseudo-instance is returned. Sub-classes need to implement the function `_get_pseudo_instance` that returns a pseudo instance. """ __singleton_lock = threading.Lock() """Lock to prevent conflicts on the singleton instance (redefined to get access) """ __singleton_instance = None """The singleton instance (redefined to get access) """ @classmethod def _get_pseudo_instance(cls): """Get pseudo instance before the real instance is initialized """ raise NotImplementedError("Subclass should implement this.") @classmethod
[docs] def get_instance(cls): """Get the singleton instance if its initialized. Returns, the pseudo instance if not. """ if cls.__singleton_instance is None: return cls._get_pseudo_instance() else: return cls.__singleton_instance
@classmethod
[docs] def reset_instance(cls): """Reset the singleton instance if its initialized. """ if cls.__singleton_instance is not None: with cls.__singleton_lock: if cls.__singleton_instance is not None: cls.__singleton_instance = None
@classmethod
[docs] def initialize(cls, *args, **kwargs): """Initializes the singleton. After calling this function, the real instance will be used. """ cls.__singleton_instance = cls(*args, **kwargs)
[docs]def delegate_special_methods(singleton): """Decorator that delegates special methods to a singleton """ def _create_delegate(name, singleton): """Creates a thunk that calls the attribute on the singleton """ def f(*args, **kwargs): """Calls the attribute on the singleton """ ret = getattr(singleton.get_instance(), name)(*args[1:], **kwargs) return ret return f def decorator(cls): """The decorator to be returned """ for name, _attr in inspect.getmembers(singleton): if (name[:2], name[-2:]) != ('__', '__'): continue if name in ['__class__', '__init__', '__dict__', '__new__', '__doc__', '__setattr__', '__abstractmethods__', '__delattr__', '__getattribute__', '__dir__']: continue setattr(cls, name, _create_delegate(name, singleton)) return cls return decorator
[docs]def delegate_singleton(singleton): """Creates an object that delegates all calls to the singleton """ # pylint: disable=too-few-public-methods @delegate_special_methods(singleton) class SingletonDelegate(object): """Passes attribute access to the singleton's instance """ def __getattr__(self, attr): """Call __getattr__ on the singleton instance """ return getattr(singleton.get_instance(), attr) @staticmethod def __dir__(): """Pass __dir__ """ return dir(singleton.get_instance()) @staticmethod def initialize(*args, **kwargs): """Pass initialize """ singleton.initialize(*args, **kwargs) return SingletonDelegate()