《笨方法学Python》习题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("open the door and smack the bear in the nose")
[('error', 'open'), ('stop', 'the'), ('error', 'door'), ('error', 'and'), ('error', 'smack'), ('stop', 'the'), ('noun', 'bear'), ('stop', 'in'), ('stop', 'the'), ('error', 'nose')]

在我们的项目目录中添加ex49文件夹,在该目录下创建init.py和parser.py文件,内容如下:

##-- coding: utf-8 --
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]
#word_list是待解析元祖列表
#返回元祖列表的第一个元祖的第一个元素
def peek(word_list):
    if word_list:
        word = word_list[0]
        return word[0]
    else:
        return None
#如果元祖列表不为空,将第一个元祖出栈,并与给定的类型进行对比,符合则返回该元祖;否则返回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)
#首先将元祖列表中的类型为‘stop’的元祖出栈,接着如果元祖列表的第一个元祖类型类型为'verb',则返回该元祖,否则抛出异常
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.")
#首先将元祖列表中的类型为‘stop’的元祖出栈,接着如果元祖列表的第一个元祖类型类型为'noun'或者'direction‘则返回该元祖
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.")
#分别将解析出的'verb','noun'/'direction'类型的元祖+给定的主语,组合成句子
def parse_subject(word_list,subj):
    verb = parse_verb(word_list)
    obj = parse_object(word_list)
    return Sentence(subj,verb,obj)
#如果元祖列表第一个元祖的类型是‘noun’,则作为主语调用parse_subject方法组成句子;
#如果第一个词的类型是‘verb’就将('noun','player')作为主语,组成句子。否则抛出异常
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)

同时在tests文件夹下创建ex49_tests.py文件,内容如下

from nose.tools import *
from ex49 import parser

def test_Sentence():
    sen = parser.Sentence(('noun','bear'),('verb','go'),('direction','north'))
    assert_equal(sen.subject, 'bear')
    assert_equal(sen.verb,'go')
    assert_equal(sen.object,'north')

def test_peek():
    assert_equal(parser.peek([('noun','bear')]),'noun')
    assert_equal(parser.peek([('verb','go'),('direction','north')]),'verb')
    assert_equal(parser.peek([('stop','of'),('number','123'),('noun','bear')]),'stop')
    assert_equal(parser.peek([]), None)
def test_match():
    assert_equal(parser.match([('direction','south')],'direction'),('direction','south'))
    assert_equal(parser.match([('stop','in'),('verb','go')],'stop'),('stop','in'))
    assert_equal(parser.match([('stop','of'),('direction','north'),('verb','go')],'stop'),('stop','of'))
    assert_equal(parser.match([('stop','in'),('verb','go')],'noun'), None)

def test_parse_verb():
    assert_equal(parser.parse_verb([('stop', 'of'), ('verb', 'go'), ('direction', 'north')]), ('verb', 'go'))
    assert_equal(parser.parse_verb([('stop', 'in'), ('verb', 'come'), ('direction', 'north')]), ('verb', 'come'))
    assert_raises(parser.ParserError, parser.parse_verb, [('stop', 'in'), ('number', 123), ('noun', 'princess')])

def test_parse_object():
    assert_equal(parser.parse_object([('stop', 'of'), ('direction', 'north'), ('verb', 'go')]),('direction', 'north'))
    assert_equal(parser.parse_object([('stop', 'in'),('noun', 'bear'), ('verb', 'come')]),('noun', 'bear'))
    assert_raises(parser.ParserError, parser.parse_object,[('verb', 'kill'), ('stop', 'of'), ('verb', 'go')])

def test_parse_subject():
    sentence = parser.parse_subject([('stop', 'of'), ('verb', 'go'), ('direction', 'north')],('noun','princess'))
    assert_equal(sentence.subject,'princess')
    assert_equal(sentence.verb,'go')
    assert_equal(sentence.object,'north')
    sentence2 = parser.parse_subject([('stop', 'in'), ('verb', 'kill'), ('noun', 'bear')],('noun','princess'))
    assert_equal(sentence2.subject, 'princess')
    assert_equal(sentence2.verb, 'kill')
    assert_equal(sentence2.object, 'bear')

def test_parse_sentence():
    sentence1 = parser.parse_sentence([('stop', 'in'), ('noun', 'bear'), ('verb', 'kill'),('direction','south')])
    assert_equal(sentence1.subject, 'bear')
    assert_equal(sentence1.verb, 'kill')
    assert_equal(sentence1.object, 'south')
    sentence2 = parser.parse_sentence([('verb', 'kill'), ('stop', 'of'), ('noun', 'bear')])
    assert_equal(sentence2.subject, 'player') 
    assert_equal(sentence2.verb,'kill')
    assert_equal(sentence2.object,'bear')
    assert_raises(parser.ParserError, parser.parse_sentence,[('stop', 'the'), ('stop', 'of'), ('direction', 'west'), ('verb', 'go')])

执行结果:

/simplegame$ nosetests
................
----------------------------------------------------------------------
Ran 16 tests in 0.010s

OK

加分习题

  1. 修改parse_函数(方法),将它们放到一个类里边,而不仅仅是独立的方法函数。 这两种程序设计你喜欢哪一种呢?
  2. 提高 parser 对于错误输入的抵御能力,这样即使用户输入了你预定义语汇之外的
    词语,你的程序也能正常运行下去。
  3. 改进语法,让它可以处理更多的东西,例如数字。
  4. 想想在游戏里你的 Sentence 类可以对用户输入做哪些有趣的事情。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容