辞書:キーと値
組み込み型dictに対して規定されたプロコトルを忠実に実現すると、二分木を辞書としても扱えるようになります。そこで、二分木を辞書に変身(?)させる過程を通して、相互の理解を深めましょう。
その前に、以下の事例に共通する、辞書のインスタンスmを生成しておきます。
>>> m = dict([(e,ord(e)) for e in "ABC"])
>>> m
{'A': 65, 'C': 67, 'B': 66}
辞書mには、文字列キーとその ASCII 値からなる要素対が含まれます。
メソッド keys を使うと
メソッドkeysを利用すると、辞書に含まれるすべてのキーを列挙したリストが得られます。すると、次のように、辞書の中にキーとして登録された、すべての文字列を列挙したリストが得られます。
>>> m.keys() ['A', 'C', 'B']
まず、二分木を表わすクラスBinTreeの傘下に、メソッドkeysに相当するものを実現します。クラスTnone/Tnodeの違いに注意しながら、読み進んでください。
class BinTree:
tab = " "*6
class Tnone(BinTree):
def keys(self): return []
class Tnode(BinTree):
def keys(self):
node = [self.word]
left = self.left .keys()
right = self.right.keys()
return left + node + right
クラスTnoneでは、左右の部分木を持たないので、単に空リスト [] をリターン値とします。クラスTnodeでは、二分木を構成する各ノードの中から、単語だけを抽出します。まず、変数nodeには、自身のノードに含まれる単語self.wordを設定します。次に、変数left,rightには、keys()によって得られる、左右の部分木に含まれる単語を列挙したリストを設定します。そして、これらのリストを連結したリストleft+node+rightをリターン値とします。
メソッド values を使うと
メソッドvaluesを利用すると、辞書に含まれるすべての値を列挙したリストが得られます。すると、次のように、辞書の中に値として登録された、すべての整数値を列挙したリストが得られます。
>>> m.values() [65, 67, 66]
メソッド items を使うと
メソッドitemsを利用すると、辞書に含まれるすべてのキーと値を要素対とするタプルを列挙したリストが得られます。すると、次のように、辞書の中に要素対(キー/値)として登録された、すべてのタプル(文字列/整数値)を列挙したリストが得られます。
>>> m.items()
[('A', 65), ('C', 67), ('B', 66)]
次に、先と同様にして、クラスBinTreeの傘下に、メソッドvalues/itemsに相当するものを実現します。
class Tnone(BinTree):
def values(self): return []
def items(self): return []
class Tnode(BinTree):
def values(self):
node = [self.count]
left = self.left .values()
right = self.right.values()
return left + node + right
def items(self):
node = [(self.word, self.count)]
left = self.left .items()
right = self.right.items()
return left + node + right
メソッドvalues/itemsについても同様です。クラスTnoneでは、左右の部分木を持たないので、単に空リスト [] をリターン値とします。クラスTnodeでは、二分木を構成する各ノードの中から、values()によって得られる、単語の出現頻度self.countだけを抽出して、これらを連結したリストをリターン値とします。同様に、items()によって得られる、単語とその出現頻度を要素対とするタプルself.word,self.countを列挙したリストをリターン値とします。
ためしてガッテン!
では実際に、これらのメソッドの動作を確認してみましょう。
>>> p good(1,as(2,,can(1,be(1,,),)),happy(2,,)) >>> p.keys() ['as', 'be', 'can', 'good', 'happy']
メソッドkeysを利用すると、二分木に含まれる単語を列挙したリストが得られます。
>>> p.values() [2, 1, 1, 1, 2]
メソッドvaluesを利用すると、二分木に含まれる単語の出現頻度を列挙したリストが得られます。
>>> p.items()
[('as', 2), ('be', 1), ('can', 1), ('good', 1), ('happy', 2)]
メソッドitemsを利用すると、単語とその出現頻度を要素対とするタプルを列挙したリストが得られます。
プロジェクトX(バツ):失敗から学べること
話を進める前に、これらのメソッドkeys/values/itemsを実現する前まで、時計の針を戻してみましょう。過去にさかのぼって、その動作を確認してみるのも一興です。
>>> p good(1,as(2,,can(1,be(1,,),)),happy(2,,)) >>> p.keys() Traceback (innermost last): ... AttributeError: 'instance' object has no attribute 'keys' >>> p.values() Traceback (innermost last): ... AttributeError: 'instance' object has no attribute 'values' >>> p.items() Traceback (innermost last): ... AttributeError: 'instance' object has no attribute 'items'
どの場合も実行時に例外オブジェクトAttributeErrorを生成すると共に、エラーメッセージ 'instance' object has no attribute '...' を出力します。これは、属性としてのメソッドkeys/values/itemsをまだ実現していないことを表わします。
洗練された OOP の世界では、「例外」さえもその例外ではなく「オブジェクト」として実現されます。つまり、ここでは例外オブジェクト(クラスAttributeErrorのインスタンス)を生成します。すると、例外オブジェクトから実行時のエラーに関する情報が得られるので、そこからメッセージを形成して、標準エラー出力に表示します。しかも、これらの作業はコンパイラーに頼らずに、自己責任で行います。Java と違って、Python の世界では「もう一人前 first-class object なのだから、親(コンパイラー)に頼らず自立しなさい」と言われ、一人前(熟練者)として扱われます。Python プログラマーに、若葉(初心者)マークは似合いません。
プロジェクトX(エックス)から得られる感動は、明日への活力の源となりますが、それを達成するには「天から授かる」幸運も欠かせません。しかし、プロジェクトX(バツ)から得られる教訓は「自らの意思で」明日の失敗を未然に防ぐ術となります。成功よりむしろ失敗から学んだことの方が「経験に基づく知識」として役立つ場面も少なくありません。例外オブジェクトから、プログラマーが何を学べるかが肝要です。
どこで真偽を審議するのか
組み込み関数boolを利用すると、指定された引数と等価な真偽値True/Falseが得られます。ここで、True/Falseは、クラスboolのインスタンスです。
>>> bool({})
False
>>> bool({"a":1})
True
boolの引数に、空の辞書 {} を指定すると、Falseが得られます。空でない辞書を指定すると、Trueが得られます。つまり、辞書のインスタンスは「真偽値」としても扱えます。しかし、組み込み型と同様に、任意のクラスのインスタンスを真偽値として扱えるようにするためには、前もって準備が必要です。
メソッド __nonzero__ は何処に
新たに定義したばかりのクラスのインスタンスは、まだ何も準備をしていないので、Trueと評価されます。ところが、メソッド__nonzero__を再定義すると、その真偽値を自由に再設定できます。
>>> class Foo: pass >>> bool(Foo()) True
メソッド__nonzero__を再定義しないと、新たなクラスFooのインスタンスは、Trueと評価されます。ところが、
>>> class Foo: ... def __nonzero__(self): return False >>> bool(Foo()) False
このメソッドを再定義すると、Falseと評価されるのが分かります。
class Tnone(BinTree):
def __nonzero__(self): return False
class Tnode(BinTree):
def __nonzero__(self): return True
メソッド__nonzero__を再定義するなら、クラスTnoneはFalseと評価され、クラスTnodeはTrueと評価されるようになります。
では実際に、これらのメソッドの動作を確認してみましょう。
>>> p good(1,as(2,,can(1,be(1,,),)),happy(2,,)) >>> bool(p) True
ここでは、二分木pは、部分木を持つので、Trueと評価されます。
>>> p = Tnone(); p . >>> bool(p) False
ここでは、二分木pは、部分木を持たないので、Falseと評価されます。
>>> p = Tnode(); p (0,,) >>> bool(p) True
ここでは、二分木pには、まだ単語とその出現頻度を登録していません。しかし、部分木を持つので、Trueと評価されます。
このようにして、メソッド__nonzero__を再定義するだけで、その真偽値を自由に設定できます。すると、True/Falseと同様に、任意の二分木のインスタンスを、そのまま条件式として指定できるようになります。
プログラミング言語 Pascal がその名前にあやかったとされる、Blaise Pascal(1623-1662)は、彼が書き綴ったノートに「人間は考える葦である」と記しています。オブジェクトは、自ら考える意思を持つことで「一人前のオブジェクト
first-classobject」として扱われ、OOP の世界で市民権を得ます。自他共に認める「一人前のプログラマー」となることを目指す皆さんなら、いつか、その意義を認めるときが訪れるでしょう。それが、コンパイラーの支配下から解放された、独立記念日(Independence Day)となるなら、その日が少しでも早く訪れることを願います。