让isinstance(obj, cls)与装饰类一起工作
我有几个类需要实现以下功能:
当调用构造函数时,如果已经存在一个相同的对象(也就是说,具有相同ID的对象),就返回那个对象。否则,就创建一个新的实例。
简单来说,就是:
>>> cls(id=1) is cls(id=1)
True
为了实现这个功能,我写了一个类装饰器,代码如下:
class Singleton(object):
def __init__(self, cls):
self.__dict__.update({'instances': {},
'cls': cls})
def __call__(self, id, *args, **kwargs):
try:
return self.instances[id]
except KeyError:
instance= self.cls(id, *args, **kwargs)
self.instances[id]= instance
return instance
def __getattr__(self, attr):
return getattr(self.cls, attr)
def __setattr__(self, attr, value):
setattr(self.cls, attr, value)
这个装饰器能达到我想要的效果,但:
@Singleton
class c(object):
def __init__(self, id):
self.id= id
o= c(1)
isinstance(o, c) # returns False
我该如何修复这个问题呢?我找到了一些相关的问题,但我就是无法将那些解决方案适应到我的情况中。
我知道有人会让我贴出一些不工作的代码,所以这里是:
def Singleton(cls):
instances= {}
class single(cls):
def __new__(self, id, *args, **kwargs):
try:
return instances[id]
except KeyError:
instance= cls(id, *args, **kwargs)
instances[id]= instance
return instance
return single
# problem: isinstance(c(1), c) -> False
def Singleton(cls):
instances= {}
def call(id, *args, **kwargs):
try:
return instances[id]
except KeyError:
instance= cls(id, *args, **kwargs)
instances[id]= instance
return instance
return call
# problem: isinstance(c(1), c) -> TypeError
2 个回答
1
作为一种替代方案,你可以用元类来创建单例类,而不是使用装饰器。元类可以像子类那样为类添加功能。使用元类的好处是,类的名字 c
直接指向类 c
本身,而不是一个 Singleton
对象或者一个包装了构造函数调用的函数。
举个例子:
class SingletonMeta(type):
"""SingletonMeta is a class factory that adds singleton functionality to a
class. In the following functions `cls' is the actual class, not
SingletonMeta."""
def __call__(cls, id, *args, **kwargs):
"""Try getting a preexisting instance or create a new one"""
return cls._instances.get(id) or cls._new_instance(id, args, kwargs)
def _new_instance(cls, id, args, kwargs):
obj = super(SingletonMeta, cls).__call__(*args, **kwargs)
assert not hasattr(obj, "id"), "{} should not use 'id' as it is " \
"reserved for use by Singletons".format(cls.__name__)
obj.id = id
cls._instances[id] = obj
return obj
def __init__(cls, classname, bases, attributes):
"""Used to initialise `_instances' on singleton class"""
super(SingletonMeta, cls).__init__(classname, bases, attributes)
cls._instances = {}
你可以这样使用它:
# python 2.x
class MySingleton(object):
__metaclass__ = SingletonMeta
# python 3.x
class MySingleton(object, metaclass=SingletonMeta):
pass
和你的装饰器相比:
class IDObject(object):
def __str__(self):
return "{}(id={})".format(type(self).__name__, self.id)
@Singleton
class A(IDObject):
def __init__(self, id):
self.id = id
class B(IDObject, metaclass=SingletonMeta):
pass
format_str = """{4} class is {0}
an instance: {1}
{1} is {1} = {2}
isinstance({1}, {0.__name__}) = {3}"""
print(format_str.format(A, A(1), A(1) is A(1), isinstance(A(1), A), "decorator"))
print()
print(format_str.format(B, B(1), B(1) is B(1), isinstance(B(1), B), "metaclass"))
输出结果:
decorator class is <__main__.Singleton object at 0x7f2d2dbffb90>
an instance: A(id=1)
A(id=1) is A(id=1) = True
isinstance(A(id=1), A) = False
metaclass class is <class '__main__.B'>
an instance: B(id=1)
B(id=1) is B(id=1) = True
isinstance(B(id=1), B) = True
8
你可以在你的装饰器类里添加一个自定义的 __instancecheck__
钩子:
def __instancecheck__(self, other):
return isinstance(other, self.cls)