笨方法学Python习题49—创建句子

创建句子

从这个小游戏的扫描器中(ex48),可得到类似下面的列表:

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

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

一个句子由以下结构组成:主语  谓语(动词)   宾语   。实际句子会复杂,但我们的目的是将上面的元组列表转换为一个语句对象—包含主、谓、宾的各个成员。

match和peek

要达到这个效果,需要下面四种工具:

1. 循环访问元组列表的方法,挺简单的
2. “匹配”(match)主谓宾设置中不同种类元组的方法
3. “窥视”(peek)潜在元组的方法, 以便做决定时用到
4. “跳过”(skip)我们不关心的内容的方法,如形容词、冠词等修饰词

把这些函数放到一个叫ex48/parser.py的文件以方便对其进行测试。使用peek函数来查看元组列表中的下一个成员,然后使用match函数取出一个元素对其进行操作。
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)

句子的文法

有了工具,现在可从元组列表中来构建句子对象了。处理流程如下:

1. 使用peek识别下一个单词
2. 若这个单词和我们的语法匹配,就调用一个函数处理文法的这部分。假设函数的名字叫parse_subject
3. 如果语法不匹配,就产生一个错误,接下来你会学到这方面的内容
4. 全部分析完以后,应该能得到一个语句对象,然后可以将其应用在我们的游戏中

演示这个过程最简单的方法就是把代码展示给你,但这个习题有不一样的要求,我给出你程序来,你要为它写出测试代码来。

下面就是用来解析简单句子的代码,其使用了ex48.lexicon这个模块:
class ParserError(Exception):

    pass

class Sentence(object):

    def __init__(self, subject, verb, object):
        # remeber 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("Expecting 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, of verb not: %s" % start)

关于异常

你已简单学过关于异常的一些内容,但还没学过怎么引发它们。这个习题代码演示了如何用前面定义的ParserError来引发异常。注意ParserError是一个定义为Exception类型的类。另外要注意我们是怎样使用raise关键字来引发异常的。

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

应该测试的东西

为习49写一个完整的测试方案,确认代码中所有的东西都能正常工作。将这些测试放到tests/parser_tests.py中,与上一习题类似。其中包括异常测试——输入一个错误的句子它会抛出一个异常来。

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

写完测试以后,你应该就能明白这段程序的工作原理了,且学会了如何为别人的程序写测试代码——非常有用的技能!!

附加练习

1. 修改parse_方法,将他们放到一个类里边, 而不仅仅是只作为一个方法。这两种程序设计那你喜欢哪一种?

2. 提高怕热摄入对错误输入的抵御能力,这样即使用户输入了你预定义语汇之外的单词,你的程序也能正常运行。

3. 改进文法,让它可处理更多的东西,如数字

4. 想想在游戏里你可以使用这个语句类对用户输入做哪些有趣的事情

常见问题回答

assert_raises老是弄不对?

确认你写的是assert_raises(exception, callable, parameters)而不是assert_raises(exception, callable(parameters))。注意第二个格式所做的其实是调用这个函数,并将函数的返回值作为参数传给assert_raises, 这样做是错误的。必须把要调用的函数和它的参数分别传入assert_raises中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值