问题
I have a class which, by design, must follow the singleton pattern. So I went ahead and implemented it using a metaclass. Everything worked nicely until a bug was reported which, in summary, said that deepcopy
-ied instances of my singleton class were not the same instances.
I can get around this bug by inheriting from a base singleton-type class, but I'd rather not, for reasons pointed out in this 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
回答1:
The docs on the copy
module 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 return self
, that ought to do the trick.
回答2:
When you need to customize class creation (not instance 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.
来源:https://stackoverflow.com/questions/9887501/deepcopy-does-not-respect-metaclass