5.15.2011

Python: TypeError: unhashable type

将棋の駒として、手番+駒の種類を保持するクラスを作る。
そして、そのクラスのインスタンスを持ち駒の数を保持するディクショナリのキーとして使ってみる。

まず単純にクラスを作っただけでは、ディクショナリそのものが正しく動作しない。(KeyError が発生)
これは比較処理においてインスタンスのアドレスが利用されるため。
同じ駒であっても、インスタンスを作るたびにアドレスが変わってしまうため、Key が不定となってしまう。

そこで演算子オーバーロードで「__eq__」、「__ne__」をカスタマイズすると、今度は「unhashable type」エラーが
発生する。
ディクショナリのキーにはハッシュ関数の適用が必須であるためである。

演算子オーバーロードで「__hash__」をカスタマイズ。
これで問題ないかと思ったが、クラスが変更可能なオブジェクトを定義している場合には「__hash__」を定義しては
いけないらしい。ディクショナリの実装においてハッシュ値が変更不能であることが要求されているためで、
オブジェクトのハッシュ値が変化すると、キーが誤ったハッシュバケツに入っていることになってしまうとのこと。
ごもっともな話だ。

最終的に、クラスを文字列やタプルのような immutable な設計にすることとした。
プロトタイピングは以下のような形。

_TURNS = frozenset(['+', '-'])
_PIECE_TYPES = frozenset([
    '* ', 'FU', 'KY', 'KE', 'GI', 'KI', 'KA', 'HI',
    'OU', 'TO', 'NY', 'NK', 'NG', 'UM', 'RY'
    ])
_PIECE_HANDS = frozenset(['FU', 'KY', 'KE', 'GI', 'KI', 'KA', 'HI'])
_PIECE_PROMOTE = {
    'FU':'TO', 'KY':'NY', 'KE':'NK', 'GI':'NG', 'KA':'UM', 'HI':'RY'
    }
_NULL_PIECE = ' * '

class PieceError(Exception): pass

class Piece(object):
  """Manages owner and type of the piece.
  This is an immutable class.
  """
  _mutable = False
  def __init__(self, csa=' * '):
    self._mutable = True
    self.csa = csa
    self._mutable = False

  def __setattr__(self, name, value):
    if self._mutable or name == '_mutable':
      super(Piece, self).__setattr__(name, value)
    else:
      raise TypeError("Can't modify immutable instance")
  def __delattr__(self, name):
    raise TypeError("Can't modify immutable instance")

  def __str__(self):
    return self.csa
  def __repr__(self):
    return 'Piece(%s)' % self.csa
  def __eq__(self, other):
    return self.csa == other.csa
  def __ne__(self, other):
    return self.csa != other.csa
  def __hash__(self):
    return self.csa.__hash__()

  def owner(self):
    return self.csa[0]
  def type_(self):
    return self.csa[1:]
  def promote(self):
    if not self.type_() in _PIECE_PROMOTE.keys():
      raise PieceError('Unpromotable piece (%s)' % self.csa)
    return Piece(self.owner() + _PIECE_PROMOTE[self.type_()])
  def demote(self):
    pass  # now thinking...

参考:
・Python リファレンスマニュアル
http://www.python.jp/doc/2.5/ref/customization.html
・Immutable object
http://code.activestate.com/recipes/577207-immutable-objectsubclass/

0 件のコメント:

コメントを投稿