如何避免创建具有相同值的对象?

时间:2018-03-25 16:14:32

标签: python python-2.x

我需要创建一个实例不能具有相同值的类。如果您使用已经使用过的值创建实例,那么您将获得旧的相同实例。

我使用特殊的类方法做到了:

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方法?

3 个答案:

答案 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创建foob的{​​{1}}属性如何从“bar”更改为“foo”。

另一种选择是使用__call__并覆盖其there方法:

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