Python伪不可变对象字段

时间:2016-05-12 18:10:11

标签: python hash immutability

我目前需要部分创建一个Python对象,并且能够更新它一段时间。虽然,一旦我将对象用作字典键,我就无法更新它。

当然有一种将字段标记为私有的解决方案,这对程序员来说主要是一个警告,我实际上会去寻找解决方案。

但我偶然发现了另一种解决方案,我想知道这是不是一个好主意,或者它是否可能只是出错了。这是:

class Foo():
    def __init__(self, bar):
        self._bar = bar
        self._has_been_hashed = False

    def __hash__(self):
        self._has_been_hashed = True
        return self._bar.__hash__()

    def __eq__(self, other):
        return self._bar == other._bar

    def __copy__(self):
        return Foo(self._bar)

    def set_bar(self, bar):
        if self.has_been_hashed:
            raise FooIsNowImmutable
        else:
            self._bar = bar

有些测试证明它可以按预期工作,我不能再使用set_bar了,比方说,我把对象用作字典键。

你怎么看?这是个好主意吗?它会反对我吗?有没有更简单的方法?这在某种程度上是一种不好的做法吗?

2 个答案:

答案 0 :(得分:2)

这样做有点脆弱,因为你永远不知道什么时候可以用作字典键,或者出于某种其他原因可能会调用它hash。一个对象不应该知道"知道"它是否被用作字典键。如果代码可能引发异常只会因为其他地方的其他代码将对象放入字典中而感到困惑。

遵循#34的Python哲学;显式优于隐式",为对象提供一个名为.finalize().lock()的方法会更安全表示对象不可变的标志。您还可以反转异常提升逻辑,以便__hash__在对象尚未锁定时引发异常(而不是在对象经过哈希处理时引发异常的突变)。

当您准备好使对象不可变时,您将调用.lock()。当你完成你需要做的任何变异时,明确地设置它是不可变的更有意义,而不是隐含地假设一旦你在字典中使用它,你就完成了变异。

答案 1 :(得分:0)

你可以这样做,但我不确定我会推荐它。为什么你需要在字典里?

它需要更多地了解对象的状态...想一个文件对象。你会把一个放在字典里吗?必须打开它才能使许多功能工作,一旦它关闭,你就不能再这样做了。用户必须在周围的代码中知道对象所处的状态。

对于文件来说,这是有意义的 - 毕竟,你通常不会在程序的大部分内部打开文件,或者如果你这样做,他们有非常明确的init和close代码;类似的东西必须对你的对象有意义。特别是如果你有一些API来接受这个对象,但是期望一个不可变的版本,而另一些拿着相同的对象,但期望改变它......

之前我使用过lock方法,它适用于你想要初始化一次的复杂的只读对象,然后确保没有人搞乱。例如。你从磁盘上加载一个(比方说英语)字典的副本......当你填充它时它必须是可变的,但是你不希望任何人不小心修改它,所以锁定它是一个好主意。我只会使用它,如果它是一次性锁定 - 你锁定和解锁的东西似乎是一个灾难的食谱。

如果你只想创建一个可以在可以使用的地方使用的版本,有两种解决方案。首先是在将其放入字典时显式创建不可变副本 - tuplefrozenset是此类行为的示例...如果要将list放入dict,你不能,但你可以先从它创建一个tuple,然后可以进行哈希处理。创建对象的frozen版本,然后通过查看对象类型来确定它是可变的还是不可变的非常清楚,因此很容易看到错误使用它的情况。

其次,如果你真的希望它可以清洗,但需要它是可变的......这实际上是合法的,但实现有点不同。它可以追溯到散列的概念......散列用于优化查找和相等。

第一个是确保你可以恢复对象...你把一些东西放在一个字典中,它的哈希值为4 - 进入插槽4.然后你修改它。然后你再去查看它,现在它已经变为9 - 在9号槽中没有任何东西,或者更糟糕的是,它是一个不同的物体,而且你已经坏了。

第二是平等 - 对于像集合这样的东西,我需要知道我的对象是否已经在那里。我可以哈希,但如果您对哈希有任何了解,您仍需要检查相等性以检查哈希冲突。

这并不排除支持__hash__ 可变,但这是不寻常的。你需要决定你的项目是什么使它相同,即使它是可变的。你需要做的是给每个对象一个唯一的id。从技术上讲,您可以使用id(self),但uuid模块之类的东西可能更好。 UUID4(或技术上,UUID4的散列)决定了散列和相等;包含相同UUID4的两个对象应该是完全相同的对象;两个具有完全相同数据但不同UUID4的对象将是不同的对象。