2016-05-12 62 views
1

我目前需要部分创建一个Python对象并能够更新一段时间。虽然,我不能一旦使用该对象作为字典键就能更新它。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一旦我,说,用我的对象作为字典的键。

您认为如何?这是个好主意吗?它会反对我吗?有更容易的方法吗?这是一种不好的做法吗?

+0

如果某人只是在您的对象上调用“hash(obj)”,或者在一个集合中使用它,它也将变得不可变。你还好吗? – BrenBarn

+0

我想它会没问题,因为当你检索一个对象的散列时,你认为它永远不会改变。所以你确实认为它会被锁定。 –

+0

注意事项:使用“@属性”会让这个更自然;命名为'bar',让getter变得微不足道('return self._bar'),并且setter会在执行set之前检查你的“被锁定”标志。 – ShadowRanger

回答

2

这样做有点脆弱,因为你永远不知道什么时候可能会用作字典键,或者因为其他原因可能会调用它。一个对象不应该“知道”它是否被用作字典键。如果有代码可能会引发异常,只是因为其他代码将某个对象放在字典中,这会让人感到困惑。

遵循Python的“显式优于隐式”的哲学,只需给对象一个名为.finalize().lock()的方法或其他方法会更安全,这会设置一个标志,指示该对象是不可变的。您还可以反转异常提升逻辑,以便__hash__在对象尚未锁定的情况下引发异常(而不是在对象被散列时引发异常)。

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

+0

谢谢,lock()函数听起来像是一种更好的选择!另外,我只是意识到,如果对象不再被用作关键字,那么可能会成为问题的是......在这种情况下,无法知道。 –

0

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

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

对于文件,这是有道理的 - 毕竟,通常不会在大部分程序中保存文件,或者如果您执行,他们有非常明确的init和close代码;类似的东西对你的对象有意义。特别是如果你有一些接受对象的API,但期望一个不可变的版本,并且其他接受相同对象但希望改变它的其他API ...

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

有两种解决方案恕我直言,如果你只是想创建一个版本,你可以在可哈希的地方使用。首先是明确地创建一个不可变的副本,当你把它放在一本字典中时 - tuplefrozenset就是这种行为的例子...如果你想把list放在dict中,你不能,但是你可以创建一个从它第一次,它可以被哈希。创建对象的frozen版本,然后通过查看对象类型非常清楚它是否应该是可变的或不可变的,所以很容易看到它被错误使用的情况。

二,如果你真的想要它可以哈希,但需要它是可变的......这实际上是合法的,但实施有点不同。它回到散列的想法...散列用于优化查找和平等。

第一种方法是确保您可以取回对象...您将某些内容放入字典中,并将其哈希值设置为4 - 在第4列中进行修改。然后对其进行修改。然后你再去查看它,现在它已经达到了9点 - 第9号槽没有任何东西,或者更糟糕的是,它是一个不同的物体,而且你已经坏了。

其次是平等 - 对于像套的事情,我需要知道我的对象是否已经在那里。我可以散列,但如果你知道有关散列的任何事情,你仍然需要检查相等性来检查散列冲突。

这并不排除支持__hash__是可变的,但它是不寻常的。你需要决定你的物品是什么使它一样,即使它是可变的。你需要做的是给每个对象一个唯一的ID。从技术上讲,你可能能够脱身id(self),但类似uuid模块可能是一个更好的可能性。 UUID4(或技术上来说,UUID4的散列)决定了散列和相等;两个包含相同UUID4的对象应该是完全相同的对象;具有完全相同数据但不同UUID4的两个对象将是不同的对象。