如何避免创建具有相同值的对象?
How to avoid creating objects with same values?
我需要创建一个 class,其实例不能具有相同的值。如果您使用已被使用的值创建实例,您将得到旧的相同实例。
我用特殊的class方法做到了:
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
方法更优雅的方法?
如果你真的想让它更优雅,在__new__
中实现重复检查,所以它会在你调用A(something)
时执行。
就在__new__
:
def __new__(cls, val=None):
for i in cls.instances:
if val == i.val:
return i
return object.__new__(cls)
你可以试试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'
或者您可以尝试构建自定义缓存 class,正如 Raymond Hettinger 在这条推文中所指出的:https://twitter.com/raymondh/status/977613745634471937.
这可以通过覆盖 __new__
method 来完成,后者负责创建 class 的新实例。每当你创建一个新实例时,你将它存储在一个字典中,如果字典包含一个匹配的实例,那么你 return 它而不是创建一个新实例:
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
此解决方案的一个缺点是,无论实例是新创建的实例还是存储在字典中的实例,都会调用 __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
注意在创建 b
时 a
的 foo
属性如何从 "bar" 更改为 "foo"。
另一种选择是使用 metaclass and override its __call__
方法:
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
我需要创建一个 class,其实例不能具有相同的值。如果您使用已被使用的值创建实例,您将得到旧的相同实例。
我用特殊的class方法做到了:
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
方法更优雅的方法?
如果你真的想让它更优雅,在__new__
中实现重复检查,所以它会在你调用A(something)
时执行。
就在__new__
:
def __new__(cls, val=None):
for i in cls.instances:
if val == i.val:
return i
return object.__new__(cls)
你可以试试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'
或者您可以尝试构建自定义缓存 class,正如 Raymond Hettinger 在这条推文中所指出的:https://twitter.com/raymondh/status/977613745634471937.
这可以通过覆盖 __new__
method 来完成,后者负责创建 class 的新实例。每当你创建一个新实例时,你将它存储在一个字典中,如果字典包含一个匹配的实例,那么你 return 它而不是创建一个新实例:
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
此解决方案的一个缺点是,无论实例是新创建的实例还是存储在字典中的实例,都会调用 __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
注意在创建 b
时 a
的 foo
属性如何从 "bar" 更改为 "foo"。
另一种选择是使用 metaclass and override its __call__
方法:
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