13.18 练习

13-1.程序设计。请列举一些面向对象编程与传统旧的程序设计形式相比的先进之处。

13-2.函数和方法的比较。函数和方法之间的区别是什么?

13-3.对类进行定制。写一个类,用来将浮点型值转换为金额。在本练习里,我们使用美国货币,但读者也可以自选任意货币。

基本任务:编写一个dollarize()函数,它以一个浮点型值作为输入,返回一个字符串形式的金额数。比如说:

13.18 练习 - 图1

dollarize()返回的金额数里应该允许有逗号(比如1,000,000)和美元的货币符号。如果有负号,它必须出现在美元符号的左边。完成这项工作后,你就可以把它转换成一个有用的类,名为MoneyFmt。

MoneyFmt类里只有一个数据值(即金额),和5个方法(你可以随意编写其他方法)。init()构造器对数据进行初始化,update()方法把数据值替换成一个新值,nonzero()是布尔型的,当数据值非零时返回True,repr()方法以浮点型的形式返回金额;而str()方法采用和dollarize()一样的字符格式显示该值。

(a)编写update()方法,以实现数据值的修改功能。

(b)以你已经编写的dollarize()的代码为基础,编写str()方法的代码。

(c)纠正nonzero()方法中的错误,这个错误认为所有小于1的数值,例如,50美分($0.50),返回假值(False)。

(d)附加题:允许用户通过一个可选参数指定是把负数数值显示在一对尖括号里还是显示一个负号。默认参数是使用标准的负号。

13-4.用户注册。建立一个用户数据库(包括登录名、密码和上次登录时间戳)类(参考练习7-5和练习9-12),来管理一个系统,该系统要求用户在登录后才能访问某些资源。这个数据库类对用户进行管理,并在实例化操作时加载之前保存的用户信息,提供访问函数来添加或更新数据库的信息。在数据修改后,数据库会在垃圾回收时将新信息保存到磁盘(参见del())。

13-5.几何。创建一个由有序数值对(x, y)组成的Point类,它代表某个点的X坐标和Y坐标。X坐标和Y坐标在实例化时被传递给构造器,如果没有给出它们的值,则默认为坐标的原点。

13-6.几何。创建一个直线/直线段类。除主要的数据属性:一对坐标值(参见上一个练习)外,它还具有长度和斜线属性。你需要覆盖repr()方法(如果需要的话,还有str()方法),使得代表那条直线(或直线段)的字符串表示形式是由一对元组构成的元组,即((x1,y1)、(x2,y2))。总结:

repr  将直线的两个端点(始点和止点)显示成一对元组。

length  返回直线段的长度-不要使用“len”,因为这样使人误解它是整型。

slope  返回此直线段的斜率(或在适当的时候返回None)

例13.11  金额转换程序(moneyfmt.py)

字符串格式类用来对浮点型值进行“打包”,使这个数值显示为带有正确符号的金额。

13.18 练习 - 图2

13.18 练习 - 图3

金额转换程序(moneyfmt.py)的主要代码如例13.11所示。网站上有带有充分文件证明(尚不完善)的版本moneyfmt.py。如果我们引入解释程序中的完整类,执行过程将和下面类似:

13.18 练习 - 图4

13.18 练习 - 图5

13-7.数据类。提供一个time模块的接口,允许用户按照自己给定时间的格式,比如:“MM/DD/YY”、“MM/DD/YYYY”、“DD/MM/YY”、“DD/MM/YYYY”、“Mon DD,YYYY”,或是标准的Unix日期格式、“Day Mon DD,HH:MM:SS YYYY”来查看日期。你的类应该维护一个日期值,并用给定的时间创建一个实例。如果没有给出时间值,程序执行时会默认采用当前的系统时间。还包括另外一些方法。

13.18 练习 - 图6

如果没有提供任何时间格式,默认使用系统时间或ctime()的格式。附加题:把这个类和练习6-15结合起来。

13-8.堆栈类。一个堆栈(Stack)是一种具有后进先出(last-in-first-out, LIFO)特性的数据结构。我们可以把它想象成一个餐盘架。最先放上去的盘子将是最后一个取下来的,而最后一个放上去的盘子是最先被取下来的。你的类中应该有push()方法(向堆栈中压入一个数据项)和pop()方法(从堆栈中移出一个数据项)。还有一个叫isempty()的布尔方法,如果堆栈是空的,返回布尔值1,否则返回0;一个名叫peek()的方法,取出堆栈顶部的数据项,但并不移除它。注意,如果你使用一个列表来实现堆栈,那么pop()方法从Pythonl.5.2版本起已经存在了。那就在你编写的新类里,加上一段代码检查pop()方法是否已经存在。如果经检查pop()方法存在,就调用这个内建的方法;否则就执行你自己编写的pop()方法。你很可能要用到列表对象;如果用到它时,不需要担心实现列表的功能(例如切片)。只要确保你写的堆栈类能够正确实现上面的两项功能就可以了。你可以用列表对象的子类或自己写个类似列表的对象,请参考例6.2。

13-9.队列类。一个队列(queue)是一种具有先进先出(first-in-first-out, FIFO)特性的数据结构。一个队列就像是一行队伍,数据从前端被移除,从后端被加入。这个类必须支持下面几种方法:

enqueue()  在列表的尾部加入一个新的元素。

dequeue()  在列表的头部取出一个元素,返回它并且把它从列表中删除。

请参见上面的练习和示例6.3。

13-10.堆栈和队列。编写一个类,定义一个能够同时具有堆栈(FIFO)和队列(LIFO)操作行为的数据结构。这个类和Perl语言中数组相像。需要实现四个方法:

shift()  返回并删除列表中的第一个元素,类似于前面的dequeue()函数。

unshift()  在列表的头部“压入”一个新元素。

push()  在列表的尾部加上一个新元素,类似于前面的enqueue()和push()方法。

pop()  返回并删除列表中的最后一个元素,与前面的pop()方法完全一样。

请参见练习13-8和练习13-9。

13-11.电子商务。

你需要为一家B2C(企业到消费者)零售商编写一个基础的电子商务引擎。你需要写一个针对顾客的类User,一个对应存货清单的类Item,还有一个对应购物车的类叫Cart。货物放到购物车里,顾客可以有多个购物车。同时购物车里可以有多个货物,包括多个同样的货物。

13-12.聊天室。你对目前的聊天室程序感到非常失望,并决心要自己写一个,创建一家新的因特网公司,获得风险投资,把广告集成到你的聊天室程序中,争取在六个月的时间里让收入翻五倍,股票上市,然后退休。但是,如果你没有一个非常酷的聊天软件,这一切都不会发生。你需要三个类:一个Message类,它包含一个消息字符串以及诸如广播、单方收件人等其他信息,一个User类,包含了进入你聊天室的某个人的所有信息。为了从风险投资者那里拿到启动资金,你加了一个Room类,它体现了一个更加复杂的聊天系统,用户可以在聊天时创建单独的“房间”,并邀请其他人加入。附加题:请为用户开发一个图廀¢化用户界面应用程序。

13-13.股票投资组合类。你的数据库中记录了每个公司的名字、股票代号、购买日期、购买价格和持股数量。需要编写的方法包括:添加新代号(新买的股票)、删除代号(所有卖出股票),根据当前价格(及日期)计算出的YTD或年回报率。请参见练习7-6。

13-14.DOS。为DOS机器编写一个Unix操作界面的shell。你向用户提供一个命令行,使得用户可以在那里输入Unix命令,你可以对这些命令进行解释,并返回相应的输出,例如:“ls”命令调用“dir”来显示一个目录中的文件列表,“more”调用同名命令(分页显示一个文件),“cat”调用“type”,“cp”调用“copy”,“mv”调用“ren”,“rm”调用“del”,等。

13-15.授权。示例13.8的执行结果表明我们的类CapOpen能成功完成数据的写入操作。在我们的最后评论中,提到可以使用CapOpen()或open()来读取文件中的文本。为什么呢?这两者使用起来有什么差异吗?

13-16.授权和函数编程。

(a)请为示例13.8中的CapOpen类编写一个writelinesO方法。这个新函数将可以一次读入多行文本,然后将文本数据转换成大写的形式,它与write()方法的区别和通常意思上的writelinesO与write()方法之间的区别相似。注意:编写完这个方法后,writelinesO将不再由文件对象“代理”。

(b)在writelines()方法中添加一个参数,用这个参数来指明是否需要为每行文本加上一个换行符。此参数的默认值是False,表示不加换行符。

13-17.数值类型子类化。在示例13.3中所看到的moneyfmt.py脚本基础上修改它,使得它可以扩展Python的浮点类型。请确保它支持所有操作,而且是不可变的。

13-18.序列类型子类化。模仿前面练习13-4中的用户注册类的解决方案,编写一个子类。要求允许用户修改密码,但密码的有效期限是12个月,过期后不能重复使用。附加题:支持“相似密码”检测的功能(任何算法皆可),不允许用户使用与之前12个月期间所使用的密码相似的任何密码。

13-19.映射类型子类化。假设在13.11.3节中字典的子类,若将keys()方法重写为:

13.18 练习 - 图7

(a)当方法keys()被调用,结果如何?

(b)为什么会有这样的结果?如何使我们的原解决方案顺利工作?

13-20.类的定制。改进脚本time60.py,见13.13.2节示例13.3。

(a)允许“空”实例化:如果小时和分钟的值没有给出,默认为零小时、零分钟。

(b)用零占位组成两位数的表示形式,因为当前的时间格式不符合要求。如下面的示例,wed应该输出为“12:05”。

13.18 练习 - 图8

(c)除了用hours(hr)和minutes (min)进行初始化外,还支持以下时间输入格式:

  • 一个由小时和分钟组成的元组(10,30);

  • 一个由小时和分钟组成的字典({‘hr’:10,‘min’:30});

  • 一个代表小时和分钟的字符串(“10:30”)。

附加题:允许不恰当的时间字符串表示形式,如“12:5”。

(d)我们是否需要实现radd()方法?为什么?如果不必实现此方法,那我们什么时候可以或应该覆盖它?

(e) repr()函数的实现是有缺陷而且被误导的。我们只是重载了此函数,这样我们可以省去使用print语句的麻烦,使它在解释器中很好的显示出来。但是,这个违背了一个原则:对于可估值的Python表达式,repr()总是应该给出一个(有效的)字符串表示形式。12:05本身不是一个合法的Python表达式,但Time60(‘12:05’)是合法的。请实现它。

(f)添加六十进制(基数是60)的运算功能。下面示例中的输出应该是19:15,而不是18:75:

13.18 练习 - 图9

13-21.装饰符和函数调用语法。第13.16.4节末尾,我们使用过一个装饰函数符把x转化成一个属性对象,但由于装饰符是Python2.4才有的新功能,我们给出了另一个适用于旧版本的语法:

13.18 练习 - 图10

执行这个赋值语句时到底发生了什么呢?为什么它和使用装饰符语句是等价的?

[1]. “sexagesimal”是源自拉丁语的名字;有时我们也说”hexagesimal”,这是一个希腊词根”hexe”和拉丁语“gesmal”的混合。

[2]. Python2.3中新增。

[3]. Python2.4中新增。