Wed, 17 Dec 2008 01:02:00 in Tech stuff | permalink
Updated 2009-03-03. Arbitrary constructor arguments (Mark's suggestion).
Updated 2008-12-30. Thread-safety.
A recent discussion has drawn my attention again to the use of the Singleton pattern in Python. Here's how and why I do this.
I don't like the module-is-a-singleton way, when you just keep your single instance as module global, because in terms of API you don't use the class to access its instance. In other words, it doesn't look like you're creating an instance.
Also, with this approach instances are created when the corresponding module is imported, which is not always desirable.
I want to instantiate classes as usual when I need this, but have one instance per class.
In Python you can customize object creation using the
__new__()
method. Consider the following simple example:
In this example the onlyclass Singleton(object): __instance__ = None def __new__(cls): if cls.__instance__ is None: cls.__instance__ = super(Singleton, cls).__new__(cls) return cls.__instance__ def __init__(self): print '__init__:', self s1 = Singleton() s2 = Singleton() assert s1 assert s2 assert s1 is s2
Singleton
instance is stored in the
__instance__
class attribute.
However, there's a problem in this snippet. Note that the
__init__()
method is called on every instantiation. This is normal
behaviour of types in Python. When you instantiate a class,
__new__()
and __init__()
are called internally.
But I want the signle instance to be created and initialized only once.
As far as I know, the only way to achieve this is metaclasses. In metaclass you can define what happens when you call its instances (which are also classes):
In this new example we define theclass SingletonType(type): def __call__(cls): if getattr(cls, '__instance__', None) is None: instance = cls.__new__(cls) instance.__init__() cls.__instance__ = instance return cls.__instance__ class Singleton(object): __metaclass__ = SingletonType def __init__(self): print '__init__:', self class OtherSingleton(object): __metaclass__ = SingletonType def __init__(self): print 'OtherSingleton __init__:', self s1 = Singleton() s2 = Singleton() assert s1 assert s2 assert s1 is s2 os1 = OtherSingleton() os2 = OtherSingleton() assert os1 assert os2 assert os1 is os2
__call__
method in metaclass where
__new__()
and __init__()
are called manually and only once.
Another benefit is that with the SingletonType
metaclass we can make any
existing class a singleton by simply adding the __metaclass__
attribute.
Finally, the above example can be easily enhanced to maintain thread-safety by adding proper locking:
import threading import thread class SingletonType(type): def __new__(mcls, name, bases, namespace): # Allocate lock, if not already allocated manually namespace.setdefault('__lock__', threading.RLock()) # Since we already using __new__, we can also initialize the # __instance__ attribute here namespace.setdefault('__instance__', None) return super(SingletonType, mcls).__new__(mcls, name, bases, namespace) def __call__(cls): cls.__lock__.acquire() try: # __instance__ is now always initialized, so no need to use default # value if cls.__instance__ is None: instance = cls.__new__(cls) instance.__init__() cls.__instance__ = instance finally: cls.__lock__.release() return cls.__instance__ class Singleton(object): __metaclass__ = SingletonType def __init__(self): print '__init__:', self class OtherSingleton(object): __metaclass__ = SingletonType def __init__(self): print 'OtherSingleton __init__:', self s1 = Singleton() s2 = Singleton() assert s1 assert s2 assert s1 is s2 os1 = OtherSingleton() os2 = OtherSingleton() assert os1 assert os2 assert os1 is os2
In this enhanced example a separate reentrant lock object (wich locks only if
already acquired by another thread) is allocated for each Singleton
class (Singleton
and OtherSingleton
). This happens at
module import time because metaclasses in Python are applied right after class
definition is closed.
Finally, in a more real world example it would be desirable to have constructors which accept arbitrary positional and keyword arguments. Thus, we can change the corresponding methods' signatures as Mark suggested in his comment:
import threading import thread class SingletonType(type): def __new__(mcls, name, bases, namespace): namespace.setdefault('__lock__', threading.RLock()) namespace.setdefault('__instance__', None) return super(SingletonType, mcls).__new__(mcls, name, bases, namespace) def __call__(cls, *args, **kw): cls.__lock__.acquire() try: if cls.__instance__ is None: instance = cls.__new__(cls, *args, **kw) instance.__init__(*args, **kw) cls.__instance__ = instance finally: cls.__lock__.release() return cls.__instance__ class Singleton(object): __metaclass__ = SingletonType def __init__(self, a, b=None): print '__init__:', self class OtherSingleton(object): __metaclass__ = SingletonType def __init__(self, a, b=None): print 'OtherSingleton __init__:', self s1 = Singleton(0, b=1) s2 = Singleton(b=3) assert s1 assert s2 assert s1 is s2 os1 = OtherSingleton(4) os2 = OtherSingleton(6, b=7) assert os1 assert os2 assert os1 is os2
Now the SingletonType is more usable as it passes through all arguments to constructor.