Exercise 49 Making Sentence的实现、进阶练习及理解(Learn Python The Hard Way: 笨办法学python)


在ex49 Making Sentence, 即构造句子这一练习中,作者提供了程序源码,要求读者写出对应的测试单元。同时,在进阶练习单元,作者建议将做好的单元测试函数,改写至类中,下面提供个人的实现方法,并对编写过程中遇到的部分代码原理,作了测试,以验证部分猜想。

作者提供的源码及个人理解

# file: .\ex48\ex48\parser.py
class ParserError(Exception):
    pass

class Sentence(object):
    def __init__(self, subj, verb, obj):
        self.subj = subj[1]
        self.verb = verb[1]
        self.obj = obj[1]

def get_type(word_list):
    # 读取的类型,如:返回('nouns', 'door')中的'nouns'
    if  word_list:
        word =  word_list[0]
        #从元组构成的列表中取出第一个元组元素。
        return word[0]
        #从元组元素中返回第一个字符串
    else:
        return None

def match(word_list, expecting):
    # 传入由多个元组构成的列表,查看第1个元组是不是expect想找的元组
    # 如果是,取走该元组。不是,则删除该元组
    if word_list:
        word = word_list.pop(0)
        # 读取列表中的第1个元素,这个元素是一个元组,如('nouns', 'door')。

        if word[0] == expecting:
            return word
            # 本例中,元组第1元素表示的是类型,
            # 如果这个类型是我们要找的类型,就把这个元组返回
        else:
            return None
            # 不管找没找到,都使word_list元素减少了1个
    else:
        return "list Null"

def skip(word_list, word_type):
    # 从word_list中跳过word_type类型的元组,直到list的第1个元素不是word_type
    while get_type(word_list) == word_type:
        match(word_list, word_type)
        # 因为match总会pop掉一个元素,才使得这个函数得以成立
        # 换成pop(word_list.pop(0))更好
        # 但我感觉函数之前的相互依赖性又太强了。match做了匹配之外更多的事情。

def parse_verb(word_list):
    # 先跳过stop类的词组(无用但合法),考察第1个元素是不是动词
    # 如果是,则取走;如果不是,不取走,报错。
    skip(word_list, 'stop')
    word_type = get_type(word_list)

    if word_type == 'verb':
        return match(word_list, 'verb')
        # 找到就取走
    else:
        raise ParserError("Expected a verb next.")

def parse_object(word_list):
    skip(word_list, 'stop')
    word_type = get_type(word_list)

    if word_type == 'noun':
        return match(word_list, 'noun')
    elif word_type == 'direction':
        return match(word_list, 'direction')
    else:
        raise ParserError("Expected a noun or direction next.")

def parse_subject(word_list):
    skip(word_list, 'stop')
    word_type = get_type(word_list)

    if word_type == 'noun':
        return match(word_list, 'noun')
    elif word_type == 'verb':
        return ('noun', 'player')
        # 当第1个词是动词是,默认地认为主语为player,也没有取走第1个元素
        # 这是合情合理的。
    else:
        raise ParserError("Expected a verb next.")

def parse_sentence(word_list):
    subj = parse_subject(word_list)
    verb = parse_verb(word_list)
    obj = parse_object(word_list)

    return Sentence(subj, verb, obj)

源码的单元测试

# file: .\ex48\test\parser_test.py
from nose.tools import *

from ex48.parser import *
#import的写法与文件存放位置息息相关

def test_get_type():
    test_list1 = [('verb', 'open'), ]

    assert_equal(get_type(test_list1), 'verb')

    test_list2 = [('', 'kill'), ('verb', 'open')]
    assert_equal(get_type(test_list2), '')

    test_list3 = [(None, None), ]
    assert_equal(get_type(test_list3), None)

def test_match():
    test_list1 = [
        ('verb', 'open'),
        ('stop', 'the'),
        ('noun', 'door'),
        ]

    assert_equal(match(test_list1, 'verb'),
                ('verb', 'open'))
    assert_equal(match(test_list1, 'stop'),
                 ('stop', 'the'))

    test_list2 = [(None, None), ]
    assert_equal(match(test_list2, 123),
                 None)
    assert_equal(test_list2, [])
    assert_equal(match(test_list2, 123),
                 "list Null")

def test_skip():
    test_list1 = [
        ('verb', 'open'),
        ('stop', 'the'),
        ('noun', 'door'),
        ]

    skip(test_list1, 'verb')

    assert_equal(test_list1[0], ('stop', 'the'))
    assert_equal(test_list1[0][0], 'stop')
    assert_equal(test_list1[0][0][0], 's')
    # 这里,我顺带测试了test_list1的第一层内部元素。很棒。

    test_list2 = [
        ('1', 'AAAA'),
        ('1', 'tDFWERhe'),
        ('noun', 'door'),
        ]

    skip(test_list2, '1')
    assert_equal(test_list2, [('noun', 'door'),])
    # 最后一个逗号加不加都一样。

    test_list3 = [
        ('2', 'AAAA'),
        ('1', 'tDFWERhe'),
        ('noun', 'door'),
        ]
    skip(test_list3, '1')
    assert_equal(test_list3[0], ('2', 'AAAA'))



def test_parse_verb():
    test_list1 = [
        ('stop', 'the'),
        ('verb', 'open'),
        ('noun', 'door'),
        ]

    assert_equal(parse_verb(test_list1),
                 ('verb', 'open'))

    assert_raises(ParserError, parse_verb, test_list1)
    # **本句含义为:让系统判断,parse_verb(test_list1)执行后
    #            是不是返回ParserError错误。

    # assert_raises参数详解:
    # 1参数:错误的类型,如本例中为片定义的ParserError
    # 2参数:你预计会出错的那个函数名。本例中,test_list1
    #       只剩下('noun', 'door')这一元素,按理运行parse_verb
    #       后肯定报错,所以将parse_verb放在2参数。注意别加括号!
    # 3、4、5参数:填入“预计会出错的那个函数,所需要的参数”。
    #       可拓展,与2参数实际需要的函数对应。
    # 现在你可以看懂上面标**的那句解释了。

    test_list2 = [
        ('noun', 'door'),
        ('a', 'b')
        ]
    assert_raises(ParserError, parse_verb, test_list2)

def test_parse_object():
    test_list1 = [
        ('noun', 'door'),
        ('direction', 'b')
        ]
    assert_equal(parse_object(test_list1), ('noun', 'door'))
    assert_equal(parse_object(test_list1), ('direction', 'b'))
    assert_raises(ParserError, parse_object, test_list1)

def test_parse_subject():
    test_list1 = [
        ('stop', 'the'),
        ('verb', 'open'),
        ('noun', 'door'),
        ]

    assert_equal(parse_subject(test_list1), ('noun', 'player'))
    parse_verb(test_list1)
    assert_equal(parse_subject(test_list1), ('noun', 'door'))
    assert_raises(ParserError, parse_subject, test_list1)

def test_parse_sentence():
    test_list1 = [
        ('stop', 'the'),
        ('verb', 'open'),
        ('noun', 'door'),
        ]

    sentence = parse_sentence(test_list1)
    assert_equal(sentence.subj,  'player')
    assert_equal(sentence.verb,  'open')
    assert_equal(sentence.obj,  'door')

    test_list2 = [
        ('verb', 'kill'),
        ('noun', 'you'),
        ('stop', 'and'),
        ('verb', 'eat'),
        ('noun', 'bear'),
    ]
    sentence2 = parse_sentence(test_list2)
    assert_equal(sentence2.subj, 'player')
    assert_equal(sentence2.verb, 'kill')
    assert_equal(sentence2.obj, 'you')

    sentence3 = parse_sentence(test_list2)
    assert_equal(sentence3.subj, 'player')
    assert_equal(sentence3.obj, 'bear')
# finish!

进阶练习改写后的源码

# file: .\ex48\ex48\parser_by_class.py
class ParserError(Exception):
    pass

class Sentence(object):
    def __init__(self, subj, verb, obj):
        self.subj = subj[1]
        self.verb = verb[1]
        self.obj = obj[1]

class Parse(object):
    def __init__(self, word_list):
        # self.sentence = Sentence()
        self.word_list = word_list

    def get_type(self):
        # 读取的类型,如:返回('nouns', 'door')中的'nouns'
        if  self.word_list:
            word =  self.word_list[0]
            #从元组构成的列表中取出第一个元组元素。
            return word[0]
            #从元组元素中返回第一个字符串
        else:
            return None

    def match(self, expecting):
        # 传入由多个元组构成的列表,查看第1个元组是不是expect想找的元组
        # 如果是,取走该元组。不是,则删除该元组
        if self.word_list:
            word = self.word_list.pop(0)
            # 读取列表中的第1个元素,这个元素是一个元组,如('nouns', 'door')。

            if word[0] == expecting:
                return word
                # 本例中,元组第1元素表示的是类型,
                # 如果这个类型是我们要找的类型,就把这个元组返回
            else:
                return None
                # 不管找没找到,都使word_list元素减少了1个
        else:
            return "list Null"

    def skip(self, word_type):
        # 从word_list中跳过word_type类型的元组,直到list的第1个元素不是word_type
        while self.get_type() == word_type:
            self.match(word_type)
            # 因为match总会pop掉一个元素,才使得这个函数得以成立
            # 换成pop(word_list.pop(0))更好
            # 但我感觉函数之前的相互依赖性又太强了。match做了匹配之外更多的事情。


    def get_verb(self):
        # 先跳过stop类的词组(无用但合法),考察第1个元素是不是动词
        # 如果是,则取走;如果不是,不取走,报错。
        self.skip('stop')
        word_type = self.get_type()

        if word_type == 'verb':
            return self.match('verb')
            # 找到就取走
        else:
            raise ParserError("Expected a verb next.")

    def get_object(self):
        self.skip('stop')
        word_type = self.get_type()

        if word_type == 'noun':
            return self.match('noun')
        elif word_type == 'direction':
            return self.match('direction')
        else:
            raise ParserError("Expected a noun or direction next.")

    def get_subject(self):
        self.skip('stop')
        word_type = self.get_type()

        if word_type == 'noun':
            return self.match('noun')
        elif word_type == 'verb':
            return ('noun', 'player')
            # 当第1个词是动词是,默认地认为主语为player,也没有取走第1个元素
            # 这是合情合理的。
        else:
            raise ParserError("Expected a verb next.")

    def get_sentence(self):
        subj = self.get_subject()
        verb = self.get_verb()
        obj = self.get_object()

        return Sentence(subj, verb, obj)

进阶练习对应的单元测试

from nose.tools import *

from ex48.parser_by_class import *

def test_get_type():
    test_list1 = [('verb', 'open'), ]
    list1 = Parse(test_list1)
    assert_equal(list1.get_type(), 'verb')

    test_list2 = [('', 'kill'), ('verb', 'open')]
    list2 = Parse(test_list2)
    assert_equal(list2.get_type(), '')

    test_list3 = [(None, None), ]
    list3 = Parse(test_list3)
    assert_equal(list3.get_type(), None)

def test_match():
    test_list1 = [
        ('verb', 'open'),
        ('stop', 'the'),
        ('noun', 'door'),
        ]
    list1 = Parse(test_list1)

    assert_equal(list1.match('verb'),
                 ('verb', 'open'))
    assert_equal(list1.match('stop'),
                 ('stop', 'the'))

    test_list2 = [(None, None), ]
    list2 = Parse(test_list2)
    assert_equal(list2.match(123),
                 None)
    assert_equal(list2.word_list, [])
    assert_equal(list2.match(123),
                 "list Null")

def test_skip():
    test_list1 = [
        ('verb', 'open'),
        ('stop', 'the'),
        ('noun', 'door'),
        ]
    list1 = Parse(test_list1)
    list1.skip('verb')

    assert_equal(test_list1[0], ('stop', 'the'))
    assert_equal(test_list1[0][0], 'stop')
    assert_equal(test_list1[0][0][0], 's')
    # 这里,我顺带测试了test_list1的第一层内部元素。很棒。

    test_list2 = [
        ('1', 'AAAA'),
        ('1', 'tDFWERhe'),
        ('noun', 'door'),
        ]
    list2 = Parse(test_list2)

    list2.skip('1')
    assert_equal(test_list2, [('noun', 'door'),])
    # 最后一个逗号加不加都一样。

    test_list3 = [
        ('2', 'AAAA'),
        ('1', 'tDFWERhe'),
        ('noun', 'door'),
        ]
    list3 = Parse(test_list3)
    list3.skip('1')
    assert_equal(test_list3[0], ('2', 'AAAA'))
    assert_equal(list3.word_list[0], ('2', 'AAAA'))

def test_get_verb():
    test_list1 = [
        ('stop', 'the'),
        ('verb', 'open'),
        ('noun', 'door'),
        ]
    list1 = Parse(test_list1)

    assert_equal(list1.get_verb(),
                 ('verb', 'open'))

    assert_raises(ParserError, list1.get_verb)

    test_list2 = [
        ('noun', 'door'),
        ('a', 'b')
        ]
    list2 = Parse(test_list2)
    assert_raises(ParserError, list2.get_verb)

def test_get_object():
    test_list1 = [
        ('noun', 'door'),
        ('direction', 'b')
        ]
    list1 = Parse(test_list1)

    assert_equal(list1.get_object(), ('noun', 'door'))
    assert_equal(list1.get_object(), ('direction', 'b'))
    assert_raises(ParserError, list1.get_object)

def test_get_subject():
    test_list1 = [
        ('stop', 'the'),
        ('verb', 'open'),
        ('noun', 'door'),
        ]
    list1 = Parse(test_list1)

    assert_equal(list1.get_subject(), ('noun', 'player'))
    list1.get_verb()
    assert_equal(list1.get_subject(), ('noun', 'door'))
    assert_raises(ParserError, list1.get_subject)

def test_get_sentence():
    test_list1 = [
        ('stop', 'the'),
        ('verb', 'open'),
        ('noun', 'door'),
        ]
    list1 = Parse(test_list1)
    # sentence = test_list1.get_sentence
    sentence = list1.get_sentence()
    assert_equal(sentence.subj,  'player')
    assert_equal(sentence.verb,  'open')
    assert_equal(sentence.obj,  'door')

    test_list2 = [
        ('verb', 'kill'),
        ('noun', 'you'),
        ('stop', 'and'),
        ('verb', 'eat'),
        ('noun', 'bear'),
    ]
    list2 = Parse(test_list2)

    sentence2 = list2.get_sentence()
    assert_equal(sentence2.subj, 'player')
    assert_equal(sentence2.verb, 'kill')
    assert_equal(sentence2.obj, 'you')

    # 下面针对list2 继续操作。
    sentence3 = list2.get_sentence()
    assert_equal(sentence3.subj, 'player')
    assert_equal(sentence3.obj, 'bear')
# finish!

总结

从进阶练习来看,以面向对象的思想,改写为class后,有以下改变:

  1. 函数在引用时的语法,更接近自然语言语法。如:
    调用原来的取动词函数parse_verb(list1),改写后,用list.get_verb()来调用,使得程序可读性更高。
  2. 实现class以后,即"封装"后,使编写者的思维更加结构化。如:
    在对list1进行操作时,编写者只能将parser.py作为一个函数库,随时记忆这个库里应当采用什么函数对list1进行处理。
    改写后,list1作为一个类的实例,list1就变成一个箱子,这个箱子里虽然也算作一个函数库,但当你处理list2以后,list1里面有什么函数,就不用关心了。
    即,你的不必将整个parser.py函数库装载进大脑,以便于编程,而可以将每个实例(每个list)看作一个盒子,你要对盒子操作时,装载盒子里面有的函数,就可以了。私以为,这实实在在地减少了大脑负荷。
  3. 可以控制函数的作用域。这与第2点有一定的内在关系。如:
    这里写的skip函数本意,仅对(‘noun’, ‘door’)一样的数据作以处理。所以当不处理此类数据时,编程者的大脑里就不必记忆这些函数,系统也不需要使用它们了。
    顺理成章地,将skip封闭入类以后,它仅由“盒子”的内部调用,编程者在编写时,对这个函数的使用也更放心,因为不需要担心它在类以外的地方出现,系统在背后帮你做完了一切。
    进一步地,在类之外,万一有需求,我们还能编写一个skip函数,新旧skip函数,互不相扰,相得益彰。
  4. 还能暴露原来函数命名的缺点。
    书本作者编写的本程序,在内部是自洽的,但作为一个盒子对外展示时,容易使调用者产生误解。如:
    word_type = get_type(word_list)这一句,是容易理解的,但其背后隐藏了1个信息:只取得word_list第1个元素的类型。改写为类时,我将这一句是改成了:word_type = self.get_type()。单看改写后的句子,似乎是将类内部的变量类型取得。这就与get_type实际作用相矛盾。
    矛盾的出现,就是缺点的暴露。
    解决这一矛盾,却也简单,将get_type、match等函数,改为get_1st_word_type、match_1st_word等即可;又或者,u将Parse类,改名为Parse_1st_word。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值