Python伪不可变对象字段
我目前需要部分创建一个Python对象,并能够在一段时间内对其进行更新。尽管如此,一旦我将对象用作字典键,我就不能更新它 当然,有一种将字段标记为private的解决方案,这主要是对程序员的一个警告,我实际上会选择这种解决方案 但我无意中发现了另一个解决方案,我想知道这是否是一个好主意,或者它是否会出现可怕的错误。这是:Python伪不可变对象字段,python,hash,immutability,Python,Hash,Immutability,我目前需要部分创建一个Python对象,并能够在一段时间内对其进行更新。尽管如此,一旦我将对象用作字典键,我就不能更新它 当然,有一种将字段标记为private的解决方案,这主要是对程序员的一个警告,我实际上会选择这种解决方案 但我无意中发现了另一个解决方案,我想知道这是否是一个好主意,或者它是否会出现可怕的错误。这是: class Foo(): def __init__(self, bar): self._bar = bar self._has_been
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了
你觉得怎么样?这是个好主意吗?它会对我不利吗?有没有更简单的方法?这是一种不好的做法吗?这样做有点脆弱,因为你永远不知道什么时候某个东西可能会被用作字典键,或者它的
哈希值可能会因为其他原因被调用。对象不应该“知道”它是否被用作字典键。仅仅因为其他代码将对象放在字典中,而有可能引发异常的代码会让人感到困惑
遵循Python的“显式优于隐式”原则,只给对象一个名为.finalize()
或.lock()
的方法或其他方法会更安全,该方法会设置一个标志,指示对象是不可变的。您还可以反转异常引发逻辑,以便\uuuuu hash\uuuu
在对象尚未锁定时引发异常(而不是在对象已被哈希时引发异常)
然后,当您准备使对象不可变时,可以调用.lock()
。当你完成了你需要做的任何变异时,显式地将它设置为不可变,而不是隐式地假设一旦你在字典中使用它,你就完成了变异。你可以这样做,但我不确定我会推荐它。你为什么要把它放在字典里
它需要更多地了解对象的状态。。。考虑一个文件对象。你能把一本放在字典里吗?它必须被打开才能让很多功能正常工作,一旦它关闭,你们就不能再做它们了。用户必须在周围的代码中知道对象处于哪个状态
对于文件来说,这是有意义的——毕竟,您通常不会在程序的很大一部分中保持打开的文件,或者如果您这样做了,它们有非常明确的init和close代码;类似的东西必须对你的目标有意义。特别是如果您有一些API接受对象,但期望不可变的版本,而其他API接受相同的对象,但期望更改它
我以前使用过lock方法,它适用于复杂的只读对象,您需要对这些对象进行一次初始化,然后确保没有人处理它们。例如,您从磁盘加载一份(比如英语)词典副本。。。在填充时,它必须是可变的,但您不希望任何人意外地修改它,因此锁定它是一个好主意。我只会在它是一次性锁的情况下使用它——你正在锁定和解锁的东西似乎是灾难的秘诀
如果您只想创建一个可以在散列位置使用的版本,那么IMHO有两种解决方案。首先,当您将其放入字典中时,显式地创建一个不可变副本-tuple
和frozenset
就是这种行为的示例。。。如果要将列表
放入目录
,则不能,但可以先从中创建一个元组
,然后对其进行哈希运算。创建对象的freezed
版本,然后通过查看对象类型可以非常清楚地知道它是可变的还是不可变的,因此很容易看到错误使用它的情况
第二,如果您真的希望它是可散列的,但需要它是可变的。。。这实际上是合法的,但实施方式有点不同。它回到了散列的概念。。。哈希用于优化查找和相等
第一个是确保您可以取回对象。。。你把一些东西放到字典里,它散列到一个4的值——进入槽4。然后修改它。然后你再去查它,现在它散列到9-插槽9中没有任何东西,或者更糟糕的是,一个不同的对象,你就崩溃了
第二个是相等性——对于集合之类的东西,我需要知道我的对象是否已经在其中。我可以散列,但如果您了解散列,您仍然需要检查相等性以检查散列冲突
这并不排除支持\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。你需要为你的物品决定什么使它相同,即使它是可变的。然后,您需要做的是为每个对象提供一个唯一的id。从技术上讲,您可以使用id(self)
,但类似uuid
模块的功能可能更好。UUID4(或者从技术上说,UUID4的散列)决定了散列和相等性;包含相同UUID4的两个对象应该是完全相同的对象;两个数据完全相同但UUID4不同的对象将是不同的对象 如果有人对您的对象调用hash(obj)
,或者在一个集合中使用它,它也将变得不可变。你同意吗?我想没关系,因为当你检索一个对象的散列时,你认为它永远不会改变。因此,您确实假设它稍后会被锁定。旁注:使用@属性将使这更自然;将其命名为bar
,让getter变得简单(返回self.\u bar
),然后setter将在执行设置之前检查您的“已锁定”标志。谢谢,lock()函数听起来确实是一种更好的选择!此外,我刚刚意识到,如果对象不再用作键,则可能会出现问题。。。在这种情况下