习题 49: 创建句子

从我们这个小游戏的词汇扫描器中,我们应该可以得到类似下面的列表:

>>> 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')]
>>>

现在让我们把它转化成游戏可以使用的东西,也就是一个 Sentence 类。

如果你还记得学校学过的东西的话,一个句子是由这样的结构组成的:

主语(Subject) + 谓语(动词 Verb) + 宾语(Object)

很显然实际的句子可能会比这复杂,而你可能已经在英语的语法课上面被折腾得够呛了。我们的目的,是将上面的元组列表转换为一个 Sentence 对象,而这个对象又包含主谓宾各个成员。

匹配(Match)和窥视(Peek)

为了达到这个效果,你需要四样工具:

  • 循环访问元组列表的方法,这挺简单的。
  • 匹配我们的主谓宾设置中不同种类元组的方法。
  • 一个“窥视”潜在元组的方法,以便做决定时用到。
  • 跳过(skip)我们不在乎的内容的方法,例如形容词、冠词等没有用处的词汇。
  • 我们使用 peek 函数来查看元组列表中的下一个成员,做匹配以后再对它做下一步动作。让我们先看看这个 peek 函数:

    def peek(word_list):
     if word_list:
      word = word_list[0]
      return word[0]
     else:
      return None
    

    很简单。再看看 match 函数:

    def match(word_list, expecting):
     if word_list:
      word = word_list.pop(0)
    

    if word[

    还是很简单,最后我们看看 skip 函数:

    def skip(word_list, word_type):
     while peek(word_list) == word_type:
      match(word_list, word_type)
    

    以你现在的水平,你应该可以看出它们的功能来。确认自己真的弄懂了它们。

    句子的语法

    有了工具,我们现在可以从元组列表来构建句子(Sentence)对象了。我们的处理流程如下:

  • 使用 peek 识别下一个单词。
  • 如果这个单词和我们的语法匹配,我们就调用一个函数来处理这部分语法。假设函数的名字叫 parse_subject 好了。
  • 如果语法不匹配,我们就 raise 一个错误,接下来你会学到这方面的内容。
  • 全部分析完以后,我们应该能得到一个 Sentence 对象,然后可以将其应用在我们的游戏中。
  • 演示这个过程最简单的方法是把代码展示给你让你阅读,不过这节习题有个不一样的要求,前面是我给你测试代码,你照着写出程序来,而这次是我给你的程序,而你要为它写出测试代码来。

    以下就是我写的用来解析简单句子的代码,它使用了 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[

    def match(word_list, expecting):
    if word_list:
    word = word_list.pop(0)

    if word[

    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 ParseError("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 ParseError("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)

    关于异常(Exception)

    你已经简单学过关于异常的一些东西,但还没学过怎样抛出(raise)它们。这节的代码演示了如何 raise。首先在最前面,你要定义好 ParserException 这个类,而它又是 Exception 的一种。另外要注意我们是怎样使用 raise 这个关键字来抛出异常的。

    你的测试代码应该也要测试到这些异常,这个我也会演示给你如何实现。

    你应该测试的东西

    为《习题 49》写一个完整的测试方案,确认代码中所有的东西都能正常工作,其中异常的测试——输入一个错误的句子它会抛出一个异常来。

    使用 assert_raises 这个函数来检查异常,在 nose 的文档里查看相关的内容,学着使用它写针对“执行失败”的测试,这也是测试很重要的一个方面。从 nose 文档中学会使用 assert_raises,以及一些别的函数。

    写完测试以后,你应该就明白了这段程序的工作原理,而且也学会了如何为别人的程序写测试代码。 相信我,这是一个非常有用的技能。

    加分习题

  • 修改 parse_ 函数(方法),将它们放到一个类里边,而不仅仅是独立的方法函数。这两种程序设计你喜欢哪一种呢?
  • 提高 parser 对于错误输入的抵御能力,这样即使用户输入了你预定义语汇之外的词语,你的程序也能正常运行下去。
  • 改进语法,让它可以处理更多的东西,例如数字。
  • 想想在游戏里你的 Sentence 类可以对用户输入做哪些有趣的事情。
  • Project Versions

    Table Of Contents

    • 习题 49: 创建句子
      • 匹配(Match)和窥视(Peek)
      • 句子的语法
      • 关于异常(Exception)
      • 你应该测试的东西
      • 加分习题

    Previous topic

    习题 48: 更复杂的用户输入

    Next topic

    习题 50: 你的第一项工作任务

    This Page

    • Show Source

    Quick search

    Enter search terms or a module, class or function name.