返回介绍

第六部分 练习题

发布于 2024-01-29 22:24:15 字数 5556 浏览 0 评论 0 收藏 0

这些练习题会让你编写一些类,并且对一些现有的代码做些实验。当然,在现有代码中问题是必定会存在的。为了运行练习题5,要么从Internet上找出类的代码来下载,要么手动输入它(相当清楚)。这些程序开始变得复杂起来,所以要确认查看了本书末尾的解答,这些解答可以作为向导。你可以在附录B中找到解答。

1.继承。编写一个名为Adder的类,导出方法add(self,x,y),作用是打印"Not Implemented"的消息。之后,定义两个Adder的子类,来实现其中的add方法:

ListAdder

有一个add方法,它会返回两个列表参数合并的结果。

DictAdder

有一个add方法,可以返回一个新的字典,该类包含两个字典参数所包含的所有元素(任意加法的定义都行)。

通过创建三个类的实例并且调用其方法来做实验。

现在,扩展Adder超类,使其在实例中通过一个构造函数保存一个对象(例如,将self.data赋值为一个列表或一个字典),并且通过__add__方法重载+运算符为add方法打补丁[例如,X+Y触发X.add(X.dada,Y)]。哪里是最适合放置构造函数和操作符重载方法的地方(也就是说,在哪一个类里)?哪种对象可以增加在类实例中?

实际上,你可能已经发现了编写方法只接受一个真正的参数更简单[例如,add(self,y)],并且add将那个参数加载到实例当前的data属性上(例如,self.data+y)。这是不是比给add传入两个参数更合理?你会说这让你的类更“面向对象”吗?

2.运算符重载。编写一个名为Mylist的类遮住(“包装”)了Python的列表。它应该重载大多数的列表操作符和运算,包括+、索引、迭代、分片以及列表方法(例如,append和sort)。查看Python参考手册,以获得所有能够支持的方法的列表。另外,为类提供一个构造函数接受现有的列表(或者一个Mylist实例),并且将其元素拷贝到实例成员中。在交互模式下测试这个类。下列是需要探讨的问题:

a.为什么在这里拷贝初始值很重要?

b.你能使用一个空分片(例如,start[:])来拷贝Mylist实例的初始值吗?

c.有一种通用的方法把列表方法调用部署到被包装的列表吗?

d.你可以让Mylist加正常的列表吗?如果是列表加Mylist实例呢?

e.像+和分片这样的操作应该返回什么类型的对象呢?如果是索引操作的返回值呢?

f.如果你使用的是最新的Python版本(2.2版或之后的版本),你可以通过嵌入一个真正的列表在一个单独的类中来实现封装类,或者通过一个子类来扩展内置的列表类型。哪一种方法更简单?为什么?

3.子类。创建一个名为MylistSub的习题2中Mylist的一个子类,让它扩展Mylist,能够在重载运算调用前通过stdout打印一条信息,并且计算调用的次数。MylistSub应该在Mylist中继承了基本的方法行为。增加了一个序列给MylistSub应该打印一条信息,增加了对+调用的计数器,并且执行了超类的方法。此外,引入了一个新的打印操作计数器到stdout的方法,并且在交互模式下实验你编写的类。你是对每个实例都计算了调用的次数,还是对每个类(对这个类的所有的实例)?要是你的程序两种都可以的话,该如何编写呢?(提示:这取决于计数成员是赋值给了哪个对象:类成员是由所有的实例所共享的,而self的成员是每个实例的数据)。

4.元类方法。编写一个名为Meta的类,有一个能够截获所有的属性点号运算的方法(包括读取和复制),并且打印其参数,在stdout中列出。创建一个Meta的实例,并且在交互模式下通过对它进行点号运算来实验。当你尝试在表达式中使用这个实例的时候会发生什么呢?用你编写的类试试加法、索引以及分片运算(注意:基于__getattr__的一个完全通用的方法在Python 2.6下有效,但在Python 3.0下无效,第30章提及了原因,并且本练习的解答中再次给出了原因)。

5.集合对象。使用在“通过嵌入扩展类型”小节中所描述的集合类进行实验。运行命令来做如下的操作。

a.创建两个整数的集合,并且通过&和|操作符表达式来计算它们的交集和并集。

b.从字符串创建一个集合,并且试着对集合进行索引运算。在类的内部调用的是哪个方法?

c.试着使用for循环迭代字符串集合中的每个元素。这次运行的是哪个方法?

d.尝试为字符串集合和一个简单的Python字符串进行交集和并集计算。这样可行吗?

e.现在,通过子类扩展集合,使其通过使用*arg的参数形式从而能够处理任意多的操作对象。(提示:参考第18章中类似的算法)。使用集合子类来计算多个操作对象的交集和并集。该如何对三个或更多的操作对象进行交集计算,因为&操作符只有左右两边?

f.怎样才能在集合类中模拟其他的列表操作?(提示:__add__能够捕获合并运算,而__getattr__可以将大多数的列表方法调用传递给被包装的列表。)

6.类树链接。在第28章“命名空间:完整的内容”和第30章“多重继承:‘混合’类”节都提到了类有一个__bases__属性,它会返回它们的超类对象的元组(在类首行中括号中的对象)。使用__bases__来扩展lister.py混合类(参考第30章),以便能够打印实例类的直接超类的名称。当完成的时候,第一行的字符串表现形式看起来应该如下所示(地址可能不尽相同)。

7.组合。通过定义4个类来模拟一个快餐订餐的场景:

Lunch

一个容器和控制器的类。

Customer

购买食物的顾客。

Employee

顾客从他那里订餐。

Food

顾客买的东西。

下面是你将要定义的类和方法。

模拟的订单流程如下。

a.Lunch类的构造函数应该创建并嵌入一个Customer的实例和一个Employee的实例,并且应该导入一个名为order的方法。当其被调用时,这个order方法应该要求Customer实例通过调用自身的placeOrder要一个订单。Customer的place Order方法应该转向要求Employee对象一个新的Food对象,通过调用Empmloyee的takeOrder方法。

b.Food对象应该保存了一个食物名的字符串(例如,"burritos"),从Lunch.order传递到Customer.placeOrder,再到Employee.takeOrder,最后到Food的构造函数。顶层的Lunch类应该也导出一个名为result的方法,它要求顾客打印从Employee通过订单收到的食物的名字(这能够用来测试你的模拟)。

注意,Lunch需要传入Employee或者自身给Customer,才能让Customer去调用Employee的方法。

在交互模式下,用已编写的类做实验,导入Lunch类,调用它的order方法来运行一个交互,之后调用它的result方法来验证Customer得到了他或她所定的食物。如果你愿意的话,也可以在定义类的文件中简单地编写一个测试案例作为自我测试的代码,编写代码时,使用在第24章中用过的__name__技巧。在这次模拟中,Customer是一个实际的作用者。如果改成由Employee在顾客/员工的交互中进行主导,你又该如何修改你的类呢?

8.动物园动物的层次。思考如图31-1所示的类树。在一个模块中编写一个包含六个类语句的集合,用Python的继承为这个生物分类建模。之后,为每一个类都增加一个speak方法,以及在顶层的Animal超类中增加一个reply方法,从而可以简单地调用self.speak来引入下面子类中不同分类的信息(这将开始一个独立的继承搜索)。最后,在Hacker类中去掉speak方法,从而可以让它使用默认的方法。当你完成以后,你的类应该是这个样子。

图 31-1 动物园的继承层次是由连接在属性继承搜索树上的类构成的。Animal有一个通用的"reply"方法,但是每个类可能都有自己的由"reply"所调用的"speak"方法

9.描绘死鹦鹉。思考如图31-2所示结构的主题。

图 31-2 这个场景由一个嵌入并指导其他三个类的实例(Customer、Clerk和Parrot)的控制器类(Scene)构成。嵌入的实例的类可以参与到继承层次中来。组合和继承常常是为了代码重复使用而组织类的相当有用的方法

编写一系列Python的类并通过组合来实现这个结构。编写你的场景对象来定义个动作方法,并且嵌入Customer实例、Clerk和rrot——所有的都应该定义一个line方法来打印出独特的消息。嵌入的对象可以继承一个通用的超类,其中定义了line并提供了简单的文本信息,或者让它们自己定义line。最后,你的类运行起来如下所示。

为什么要在意:大师眼中的OOP

当我开始教Python类的时候,无一例外地发现班上有两种平分秋色的现象。那些曾经使用过OOP的人很强烈地表示了他们的赞同,而那些没有OOP经验的人则开始眼神呆滞(要么就是开始打盹)。该技术背后的要点只是没有体现出来。

像这样的书籍,我大费笔墨地介绍了很多内容,就像第25章新的宏观概述以及第27章的渐进教程,如果你已经开始觉得OOP只不过是计算机科学中毫无意义的崇拜对象的话,那么你也许应该开始重新复习这一部分了。

在真实的课堂上,为了帮助那些新手上路(并且让他们保持清醒),我已经知道了不能再停止向听众中的专家询问他们为何使用OOP这样的问题了。如果这个话题对于你来说是新的话,他们提供的结果或许会遮住一些OOP目的的一些光芒。

我进行了少许的加工,总结了多年来我的学生所举出的使用OOP的最常见原因,如下所示。

代码重用

这很简单(并且是使用OOP最主要的原因)。通过支持继承,类允许通过定制来编程,而不是每次都从头开始一个项目。

封装

在对象接口后包装其实现的细节,从而隔离了代码的修改对用户产生的影响。

结构

类提供了一个新的本地作用域,最小化了变量名冲突。它们还提供了一种编写和查找实现代码,以及去管理对象状态的自然场所。

维护性

类自然而然地促进了代码的分解,这让我们减少了冗余。多亏支持类的结构以及代码重用,这样每次只需要修改代码中一个拷贝就可以了。

一致性

类和继承可以实现通用的接口。这样你的代码有了统一的外表和观感,这样也简化了代码的调试、理解以及维护。

多态

这更像是一个OOP的属性。而不是一条使用它的理由,但是通过广泛地支持代码,多态让代码更灵活和有了广泛的适用性,因此有了更好的可重用性。

其他

此外,学生们给出的使用OOP的最重要理由就是:这在一份简历上看起来棒极了(好吧,我把这个当成一个笑话,但是如果你打算在如今的软件领域工作的话,熟悉OOP是相当重要的)。

最后,记住我在这一部分开始说过的:在使用OOP一段时间以后,你才会完完全全地感激它。选择一个项目,研究更大的例子,通过练习来实现(做你觉得适合OO代码的一切)。它值得你努力。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文