I have a class which, by design, must follow thesingletonpattern. So I went ahead and implemented it using ametaclass. Everything worked nicely until a bug was reported which, in summary, said that
deepcopy-ied instances of mysingletonclass were not the same instances.
I can get around this bug by inheriting from a basesingleton-type class, but I'd rather not, for reasons pointed out inthis question.
A working example of this issue is presented below:
class SingletonMeta(type): def __init__(cls, name, bases, dict): super(SingletonMeta, cls).__init__(name, bases, dict) cls.instance = None def __call__(cls,*args,**kw): print "SingletonMeta __call__ was called" if cls.instance is None: cls.instance = super(SingletonMeta, cls).__call__(*args, **kw) return cls.instance class MyClass1(object): __metaclass__ = SingletonMeta class SingletonBase(object): _instance = None def __new__(class_, *args, **kwargs): print "SingletonBase __new__ was called" if not isinstance(class_._instance, class_): class_._instance = object.__new__(class_, *args, **kwargs) return class_._instance class MyClass2(SingletonBase): pass from copy import deepcopy as dcp mm1 = MyClass1() mm2 = dcp(mm1) print "mm1 is mm2:", mm1 is mm2 mb1 = MyClass2() mb2 = dcp(mb1) print "mb1 is mb2:", mb1 is mb2
SingletonMeta __call__ was called mm1 is mm2: False SingletonBase __new__ was called SingletonBase __new__ was called mb1 is mb2: True
Can you give me any pointers as to how should one resolve this issue? I'm running Python 2.7.X
The docs on the
copymodule say this:
In order for a class to define its own copy implementation, it can define special methods
__deepcopy__(). [...] The latter is called to implement the deep copy operation; it is passed one argument, the memo dictionary. [...]
So if you declare these to return
self, that ought to do the trick.
When you need to customize class creation (notinstance creation), you do it in the
__new__method of the metaclass:
def __new__(cls, name, bases, dict): dict['__deepcopy__'] = dict['__copy__'] = lambda self, *args: self return super(SingletonMeta, cls).__new__(cls, name, bases, dict)
and your test will give
SingletonMeta __call__ was called mm1 is mm2: True
You need to define
__copy__as well or even shallow copies will result in new instances.
Glad that my solution in that thread came in handy.