はてなキーワードしりとりを改造・その4
記事にも書いていましたが前回のコードは例外処理があまりにも煩雑だったので改めて例外処理を書きなおしました。
どうすればすっきり分かりやすくて且つ期待通りの例外処理ができるかを考えるためにまず処理したい例外を整理します。
- 入力された単語が既に使用済みの単語であるという例外
- 入力された単語がはてなキーワードに登録されていないという例外
- はてなキーワードAPIが正しいXMLを返してくれないという例外→はてなの不具合かも? - 主にプログラムを勉強するブログ
- 入力された単語の頭文字が前回の単語の末尾の文字と異なっているという例外
- 入力された単語の末尾の文字が「ん」であるという例外
この5つの例外をどう処理するかです。
前回のコードではこれらをすべてExceptionクラスのインスタンスとして発生させ、実際にどの例外が発生したかは発生場所と引数をみて判断していました。
ただそのやり方ですと発生場所を知るためにtry文を処理ごとに分けて記述する必要があるためmain関数がtry文だらけになってしまっていました。
そこで今回は例外が発生し得る範囲全体を単一のtry文で囲み、main関数内のtry文はそれ1つのみにする方向で書くことにします。
どの例外が発生したかを最も単純に区別するには5つの例外それぞれに個別の例外クラスを作れば良いのだとは思うのですが、それだとこの短いプログラム中に5つも例外処理のためだけのクラスを記述することになるためむしろ煩雑さが増す気がします。
そこでShiritoriRuleErrorという独自の例外クラスを1つだけ作ってそこで上の1〜5の例外すべてを受けることにします。
3の例外はしりとりのルールとは無関係ですがここに含めます。
どの例外が発生したかはkindというプロパティで表すことにします。
以上をふまえコメントで教えていただいた辞書に対するfor文の書き方の修正と共に書きなおしたものが以下です。
# 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: raise ShiritoriRuleError(u"UniquenessError", 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 furigana is None: raise ShiritoriRuleError(u"DataNotFound") elif furigana is False: raise ShiritoriRuleError(u"XMLError") else: unicode.__init__(self, furigana) def exam(self, initial): """最初の文字と最後の文字を調べて適格か否か判断""" if not self.startswith(initial): raise ShiritoriRuleError(u"ILError") if self.endswith(u"ん"): raise ShiritoriRuleError(u"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: nextIL = toReplace[nextIL] return nextIL class ShiritoriRuleError(Exception): """しりとりのルール違反をすべて受け取る""" def __init__(self, kind, value=None): self.kind = kind self.value = value 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) furigana = Furigana(furiganadict[wordU]) furigana.exam(initial) except ShiritoriRuleError as e: if e.kind == u"UniquenessError": print u"この単語は" + str(e.value) + u"番目に使いました。" continue elif e.kind == u"DataNotFound": print u"この単語ははてなキーワードに登録されていませんでした。" continue elif e.kind == u"XMLError": print u"xmlデータを正しく受信できませんでした。他の単語を試してください。" continue elif e.kind == u"ILError": print u"次の文字は「" + initial + u"」です!" continue elif e.kind == u"FLError": print u"「ん」がついたよ" break print wordU initial = furigana.creatIL() usedwords[wordU] = len(usedwords) + 1 print u"終了です" if __name__ == "__main__": main()