はてなキーワードしりとりを改造・その3

前回のコードは処理の流れ自体はほぼ満足のいくものだったのですが、main関数に直接記述している処理が多かったのでそれを減らすように書き直してみました。
コードがかなり長くなってしまったので今回はエントリの最後にコードを載せています。

前回のコードでmain関数に記述していた処理は次の3つです。

  1. 入力された単語が使用済みかどうかの判断
  2. 入力された単語がしりとりのルール上適格かどうかの判断
  3. しりとりのキモである「次の文字」の用意

このうち1.の処理は入力を受け付けたらすぐにチェックできることなので入力を受け付ける関数input_reader内に記述しました。
2.と3.の処理はどちらも単語の「読み仮名」と密接に関係した処理なのでこれら処理をメソッドにもつFuriganaクラスを新たに作成しそこに記述しました。

同じ単語が使用される、ルール上適格でない単語が入力されるなどの処理をすべて例外として扱い、main関数はこの例外処理のみを担当するようにしました。

この結果main関数がtry分だらけになってしまい前以上に気持ち悪くなった気もするのですが・・。全体を1つのtry文に含めてしまうことも考えたのですがPEP8でtry文内のコードは小さい方が良いとのことだったので分けてみました。

# coding: utf-8

import urllib
import urllib2
from xml.dom.minidom import parse

def input_reader(text, usedwords):
    """入力を受け取ってunicodeオブジェクトにして返す"""
    
    word = raw_input(text)
    
    wordU = unicode(word, "utf-8")
    
    if wordU in usedwords.keys():
        raise Exception(usedwords[wordU])
    
    return wordU

class HatenaFuriganaDict(dict):
    """入力された単語とそのふりがなの組み合わせを辞書として管理する
       はてなキーワードに登録されていなかった単語の値はNone
       APIから正しいxmlが受信できない単語の値はFalseとする
    """
    @staticmethod
    def _ask_hatena(word):
        """__missing__から呼ばれてwordをはてなキーワードAPIに問い合わせる"""
        
        #URLエンコード
        encoded_word = urllib.quote(word.encode("utf-8"))
        
        #URLを作成
        requestURL = ("http://search.hatena.ne.jp/keyword?word="
                       + encoded_word
                       + "&mode=rss&ie=utf8&page=1")
        
        return urllib2.urlopen(requestURL, None)
    
    def __missing__(self, word):
        """この辞書に登録されていないwordが参照された場合に呼ばれ
           APIに問い合わせを行い返答する。さらにその結果を辞書に登録する
        """
        try:
            dom = parse(self._ask_hatena(word))
        except:
            self[word] = False
            return self[word]
        
        items = dom.getElementsByTagName("item")
        hatenaNS = "http://www.hatena.ne.jp/info/xmlns#" #XMLのNS
    
        if items and (items[0].getElementsByTagName("title"))[0].firstChild.data == word:
            self[word] = (items[0].getElementsByTagNameNS(hatenaNS, "furigana"))[0].firstChild.data
        else:
            self[word] = None
        
        return self[word]

class Furigana(unicode):
    """しりとりのルールに関連する処理を担う"""
    def __init__(self, furigana):
        if not furigana:
            raise Exception(furigana)
        else:
            unicode.__init__(self, furigana)
    
    def exam(self, initial):
        """最初の文字と最後の文字を調べて適格か否か判断"""
        if not self.startswith(initial):
            raise Exception("ILError")
        
        if self.endswith(u"ん"):
            raise Exception("FLError")
    
    def creatIL(self):
        """次の単語用の「次の文字」を作成する"""
        toReplace = {u"ゃ":u"や", u"ゅ":u"ゆ", u"ょ":u"よ", u"を":u"お", u"っ":u"つ"
                     , u"ぁ":u"あ", u"ぃ":u"い", u"ぅ":u"う", u"ぇ":u"え", u"ぉ":u"お"}
        
        if self.endswith(u"ー"):
            nextIL = self[-2:-1]
        else:
            nextIL = self[-1:]
        
        if nextIL in toReplace.keys():
            nextIL = toReplace[nextIL]
        
        return nextIL


def main():
    furiganadict = HatenaFuriganaDict() # API問い合わせた単語をキー読み仮名を値として管理する
    usedwords = {} # 正答の単語をキー何番目に使ったかを値として管理する
    
    initial = u"あ" #しりとりの最初の文字
    
    print (u"最初のもじは「" + initial + u"」です")
    
    while True:
        try:
            wordU = input_reader(u"次の単語を入力してください。次は"
                                  + str(len(usedwords) + 1)
                                  + u"語目です。"
                                  , usedwords)
        except Exception as e:
            print u"この単語は" + str(e.args[0]) + u"番目に使いました。"
            continue
        
        try:
            furigana = Furigana(furiganadict[wordU])
        except Exception as e:
            if e.args[0] is None:
                print u"この単語ははてなキーワードに登録されていませんでした。"
            
            elif e.args[0] is False:
                print u"xmlデータを正しく受信できませんでした。他の単語を試してください。"
            
            continue
        
        try:
            furigana.exam(initial)
        except Exception as e:
            if e.args[0] == "ILError":
                print u"次の文字は" + initial + u"です!"
                continue
            elif e.args[0] == "FLError":
                print u"んがついたよ"
                break
        
        print wordU
        
        initial = furigana.creatIL()
        
        usedwords[wordU] = len(usedwords) + 1
    
    print u"終了です"

if __name__ == "__main__":
    main()