從我們這個小游戲的詞匯掃描器中,我們應該可以得到類似下面的列表:
>>> from ex48 import lexicon
>>> print lexicon.scan("go north")
[('verb', 'go'), ('direction', 'north')]
>>> print lexicon.scan("kill the princess")
[('verb', 'kill'), ('stop', 'the'), ('noun', 'princess')]
>>> print lexicon.scan("eat the bear")
[('verb', 'eat'), ('stop', 'the'), ('noun', 'bear')]
>>> print lexicon.scan("open the door and smack the bear in the nose")
[('error', 'open'), ('stop', 'the'), ('noun', 'door'), ('error', 'and'),
('error', 'smack'), ('stop', 'the'), ('noun', 'bear'), ('stop', 'in'),
('stop', 'the'), ('error', 'nose')]
>>>
現(xiàn)在讓我們把它轉(zhuǎn)化成游戲可以使用的東西,也就是一個 Sentence 類。
如果你還記得學校學過的東西的話,一個句子是由這樣的結(jié)構(gòu)組成的:
主語(Subject) + 謂語(動詞 Verb) + 賓語(Object)
很顯然實際的句子可能會比這復雜,而你可能已經(jīng)在英語的語法課上面被折騰得夠嗆了。我們的目的,是將上面的元組列表轉(zhuǎn)換為一個 Sentence 對象,而這個對象又包含主謂賓各個成員。
為了達到這個效果,你需要四樣工具:
我們使用 peek 函數(shù)來查看元組列表中的下一個成員,做匹配以后再對它做下一步動作。讓我們先看看這個 peek 函數(shù):
def peek(word_list):
if word_list:
word = word_list[0]
return word[0]
else:
return None
很簡單。再看看 match 函數(shù):
def match(word_list, expecting):
if word_list:
word = word_list.pop(0)
if word[0] == expecting:
return word
else:
return None
else:
return None
還是很簡單,最后我們看看 skip 函數(shù):
def skip(word_list, word_type):
while peek(word_list) == word_type:
match(word_list, word_type)
以你現(xiàn)在的水平,你應該可以看出它們的功能來。確認自己真的弄懂了它們。
有了工具,我們現(xiàn)在可以從元組列表來構(gòu)建句子(Sentence)對象了。我們的處理流程如下:
演示這個過程最簡單的方法是把代碼展示給你讓你閱讀,不過這節(jié)習題有個不一樣的要求,前面是我給你測試代碼,你照著寫出程序來,而這次是我給你的程序,而你要為它寫出測試代碼來。
以下就是我寫的用來解析簡單句子的代碼,它使用了 ex48.lexicon 這個模組。
class ParserError(Exception):
pass
class Sentence(object):
def __init__(self, subject, verb, object):
# remember we take ('noun','princess') tuples and convert them
self.subject = subject[1]
self.verb = verb[1]
self.object = object[1]
def peek(word_list):
if word_list:
word = word_list[0]
return word[0]
else:
return None
def match(word_list, expecting):
if word_list:
word = word_list.pop(0)
if word[0] == expecting:
return word
else:
return None
else:
return None
def skip(word_list, word_type):
while peek(word_list) == word_type:
match(word_list, word_type)
def parse_verb(word_list):
skip(word_list, 'stop')
if peek(word_list) == 'verb':
return match(word_list, 'verb')
else:
raise ParserError("Expected a verb next.")
def parse_object(word_list):
skip(word_list, 'stop')
next = peek(word_list)
if next == 'noun':
return match(word_list, 'noun')
if next == 'direction':
return match(word_list, 'direction')
else:
raise ParserError("Expected a noun or direction next.")
def parse_subject(word_list, subj):
verb = parse_verb(word_list)
obj = parse_object(word_list)
return Sentence(subj, verb, obj)
def parse_sentence(word_list):
skip(word_list, 'stop')
start = peek(word_list)
if start == 'noun':
subj = match(word_list, 'noun')
return parse_subject(word_list, subj)
elif start == 'verb':
# assume the subject is the player then
return parse_subject(word_list, ('noun', 'player'))
else:
raise ParserError("Must start with subject, object, or verb not: %s" % start)
你已經(jīng)簡單學過關于異常的一些東西,但還沒學過怎樣拋出(raise)它們。這節(jié)的代碼演示了如何 raise 前面定義的 ParserError。注意 ParserError 是一個定義為 Exception 類型的 class。另外要注意我們是怎樣使用 raise 這個關鍵字來拋出異常的。
你的測試代碼應該也要測試到這些異常,這個我也會演示給你如何實現(xiàn)。
為《習題 49》寫一個完整的測試方案,確認代碼中所有的東西都能正常工作,其中異常的測試——輸入一個錯誤的句子它會拋出一個異常來。
使用 assert_raises 這個函數(shù)來檢查異常,在 nose 的文檔里查看相關的內(nèi)容,學著使用它寫針對“執(zhí)行失敗”的測試,這也是測試很重要的一個方面。從 nose 文檔中學會使用 assert_raises,以及一些別的函數(shù)。
寫完測試以后,你應該就明白了這段程序的工作原理,而且也學會了如何為別人的程序?qū)憸y試代碼。 相信我,這是一個非常有用的技能。