我需要创建一个实例不能具有相同值的类。如果您使用已经使用过的值创建实例,那么您将获得旧的相同实例。
我使用特殊的类方法做到了:
class A():
instances = []
def __init__(self, val):
self.val = val
@classmethod
def new(cls, val):
"""
Return instance with same value or create new.
"""
for ins in cls.instances:
if ins.val == val:
return ins
new_ins = A(val)
cls.instances.append(new_ins)
return new_ins
a1 = A.new("x")
a2 = A.new("x")
a3 = A.new("y")
print a1 # <__main__.A instance at 0x05B7FD00> S\ /M\
print a2 # <__main__.A instance at 0x05B7FD00> \A/ \E
print a3 # <__main__.A instance at 0x05B7FD28>
有没有办法让它更优雅,而不使用.new
方法?
答案 0 :(得分:3)
您可以尝试functools.lru_cache
。
例如:
from functools import lru_cache
class A:
@lru_cache()
def __new__(cls, arg):
return super().__new__(cls)
def __init__(self, arg):
self.n = arg
样本用法:
>>> a1 = A('1')
>>> a2 = A('1')
>>> a1 is a2
True
>>> a1.n
'1'
>>> a2.n
'1'
另外,您可以尝试构建自定义缓存类,正如Raymond Hettinger在本推文中指出的那样:https://twitter.com/raymondh/status/977613745634471937。
答案 1 :(得分:2)
如果您真的想让它更优雅,请在__new__
中实施重复检查,以便在您致电A(something)
时执行。
只需在__new__
中执行此操作:
def __new__(cls, val=None):
for i in cls.instances:
if val == i.val:
return i
return object.__new__(cls)
答案 2 :(得分:2)
这可以通过覆盖__new__
method来完成,metaclass负责创建类的新实例。无论何时创建新实例,都将其存储在dict中,如果dict包含匹配的实例,则返回它而不是创建新实例:
class A:
instances = {}
def __new__(cls, val):
try:
return cls.instances[val]
except KeyError:
pass
obj = super().__new__(cls)
cls.instances[val] = obj
return obj
def __init__(self, val):
self.val = val
a = A(1)
b = A(2)
c = A(1)
print(a is b) # False
print(a is c) # True
此解决方案的一个缺点是,无论实例是新创建的实例还是存储在dict中的实例,都将调用__init__
方法。如果您的构造函数有不良副作用,这可能会导致问题:
class A:
...
def __init__(self, val):
self.val = val
self.foo = 'foo'
a = A(1)
a.foo = 'bar'
b = A(1)
print(a.foo) # output: foo
注意a
创建foo
时b
的{{1}}属性如何从“bar”更改为“foo”。
class MemoMeta(type):
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
cls.instances = {}
return cls
def __call__(cls, val):
try:
return cls.instances[val]
except KeyError:
pass
obj = super().__call__(val)
cls.instances[val] = obj
return obj
class A(metaclass=MemoMeta):
def __init__(self, val):
self.val = val
self.foo = 'foo'
这绕过了在现有实例上调用__init__
的问题:
a = A(1)
a.foo = 'bar'
b = A(1)
print(a.foo) # output: bar