习题 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)?

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

  1. 循环访问元组列表的方法,这挺简单的。
  2. 匹配我们的主谓宾设置中不同种类元组的方法。
  3. 一个“窥视”潜在元组的方法,以便做决定时用到。
  4. 跳过(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[0] == expecting:
            return word
        else:
            return None
    else:
        return None

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

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

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

句子的语法?

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

  1. 使用 peek 识别下一个单词。
  2. 如果这个单词和我们的语法匹配,我们就调用一个函数来处理这部分语法。假设函数的名字叫 parse_subject 好了。
  3. 如果语法不匹配,我们就 raise 一个错误,接下来你会学到这方面的内容。
  4. 全部分析完以后,我们应该能得到一个 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[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)

关于异常(Exception)?

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

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

你应该测试的东西?

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

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

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

加分习题?

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

Project Versions

Table Of Contents

Previous topic

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

Next topic

习题 50: 你的第一个网站

This Page

时时彩东森平台 什么时时彩平台有钱送 时时彩后三组选经验 江西时时彩走试 四季彩平台下载
时时彩有人赚到钱了吗 时时彩有积分的平台 时时彩赚钱 江西时时彩定胆杀号 平刷王时时彩软件手机
时时彩学习资料 时时彩开户注册 超神时时彩软件手机版 时时彩平台角模式 时时彩娱乐平台源码
时时彩后一技巧 时时彩黑马博客 江西时时彩走势360 太原时时彩开奖视频 外卖平台靠什么赚钱
牛牛开挂随意控制点 分分彩的稳赚方法 宁夏乐彩十一选五 十一选五开奖信息 韩国快乐8开奖结果查询
上海11选5直三的技巧 重庆体彩幸运农场 北京pk拾单双稳赚技巧 北京赛车pk10历史记录 山西体彩新11选5开奖结果1月3号
新疆风采喜乐彩走势图 广东十一选五缩水 湖南幸运赛车的规律 pk10杀号技巧北京赛车pk10技巧 河南体育彩票网
北京赛车pk10技巧 江西多乐彩 三分彩开奖结果 时时彩中奖规则 甘肃快3开奖结果查询结果