如何重用现有对象以实现不可变对象?

18 投票
3 回答
4631 浏览
提问于 2025-04-16 12:47

在Python中,为什么可以重复使用已经存在的相等的不可变对象(就像对待str字符串那样)?这是不是只需要定义一个__hash__方法就可以,还是说需要更复杂的操作?

3 个回答

3

我觉得你需要保持一个字典 {args: object},用来存储已经创建的实例。然后,你需要重写这个类的 __new__ 方法,去检查这个字典,如果对象已经存在,就返回那个对象。需要注意的是,我还没有实现或测试这个想法。当然,字符串是在C语言层面处理的。

4

这里有两种“软件工程”解决方案,不需要深入了解Python的底层知识。它们适用于以下情况:

第一种情况:如果你的类的对象是用相同的构造参数创建的,那么这些对象就是“相等”的,并且这种相等关系在创建后不会改变。解决方案:使用一个工厂来对构造参数进行哈希处理:

class MyClass:
  def __init__(self, someint, someotherint):
    self.a = someint
    self.b = someotherint

cachedict = { }
def construct_myobject(someint, someotherint):
  if (someint, someotherint) not in cachedict:
    cachedict[(someint, someotherint)] = MyClass(someint, someotherint)
  return cachedict[(someint, someotherint)]

这种方法基本上限制了你的类的实例,每一对不同的输入只能对应一个独特的对象。不过,这种方法也有明显的缺点:并不是所有类型都容易进行哈希处理等等。

第二种情况:你的类的对象是可变的,它们的“相等性”可能会随着时间的推移而改变。解决方案:定义一个类级别的相等实例注册表:

class MyClass:
  registry = { }

  def __init__(self, someint, someotherint, third):
    MyClass.registry[id(self)] = (someint, someotherint)
    self.someint = someint
    self.someotherint = someotherint
    self.third = third

  def __eq__(self, other):
    return MyClass.registry[id(self)] == MyClass.registry[id(other)]

  def update(self, someint, someotherint):
    MyClass.registry[id(self)] = (someint, someotherint)

在这个例子中,具有相同的 someint, someotherint 组合的对象是相等的,而第三个参数则不影响相等性。关键是要保持 registry 中的参数同步。作为 update 的替代方案,你也可以重写你的类中的 getattrsetattr;这样可以确保任何赋值操作 foo.someint = y 都会与类级别的字典保持同步。可以在这里查看一个示例 这里

18

如果你想通过类的构造函数来创建对象,并且希望它返回一个已经创建过的对象,那么你需要提供一个 __new__ 方法(因为当你到达 __init__ 时,对象已经被创建了)。

这里有一个简单的例子 - 如果用来初始化的值之前已经出现过,那么就会返回一个已经创建的对象,而不是新创建一个:

class Cached(object):
    """Simple example of immutable object reuse."""

    def __init__(self, i):
        self.i = i

    def __new__(cls, i, _cache={}):
        try:
            return _cache[i]
        except KeyError:
            # you must call __new__ on the base class
            x = super(Cached, cls).__new__(cls)
            x.__init__(i)
            _cache[i] = x
            return x

请注意,在这个例子中,你可以使用任何可以哈希的值来初始化。为了证明对象确实被重用了:

>>> a = Cached(100)
>>> b = Cached(200)
>>> c = Cached(100)
>>> a is b
False
>>> a is c
True

撰写回答