deepcopy does not respect metaclass

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 thatdeepcopy-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

Output:

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 thecopymodule say this:

In order for a class to define its own copy implementation, it can define special methods__copy__()and__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 returnself, 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.

What Others Are Reading