强基初中数学&学Python——第二十一课 面向对象编程

    我们已经有了功能强大的函数,很多事情都可以做了,为什么还要对象?在编程中,什么是对象呢?

    我们的函数功能是强大,不错,但有一件事它做不了——保持数据到下一次的调用。例如逐科输入成绩,统计总分,如果不借助函数体外能保持数据的变量,函数是无法完成的:

显示“sum”变量还没初始化,但怎样初始化呢?第一科时初始化sum=0,但第二科应该初始化为第一科的分数,但函数根本就没有保持第一科的分数,看来,这工作函数是无法干了。

    于是,编程大师们引入了一种“特殊的函数",这种函数可以保持数据,并且内部还有“函数”,这就是“对象”了。可以保持数据的变量就是对象的属性,对象内部的“函数”就是对象的方法。在程序中使用对象,就是面向对象编程了。运用面向对象编程对上面的例子进行改造:

,结果打印出:

    要了解对象,首先要了解创建对象的模板——类。

    在Python中定义一个类的语法:

class   类名:

    属性和方法定义

例如上一例子中定义了统计成绩的一个类。创建类的关键字是class;类名也有一套习惯俗成的方法——驼峰格式:每个单词的首字母大写,连在一起就像驼峰(例如:ScoreSum);类的属性就是类代码体内的变量,定义方法与其他类型的变量没有差别;类的方法就是类代码体内的函数,与函数的定义方法大同小异。

    现在只讨论类方法的“小异”部分——方法的第一个参数self。这个是类方法必须要有的参数,与普通函数不一样,普通函数可以没有参数。那这个参数是什么呢?

    self从字面解释——自己,难道是使用这个方法的对象自己吗?是的,就是使用这个方法的对象自己。例子中是"ss”这个对象调用addOneScore这个方法,所以在这过程中,self就是ss对象。但请看调用的过程:

ss.addOneScore(118.5)   #加入一科成绩

,参数中没有ss对象本身啊?是的,调用类方法时,不需要把对象本身也写进参数中,Python会把对象本身自动添加到第一个参数中。

    self可以使用别的名字(比如"me")吗?让我们来试一试:

,可见改名完全没有问题,不过为了便于大家交流,习惯统一使用self罢了。

    既然类是对象的模板,那对象创建时(例如:ss = ScoreSum()),不就把模板中的属性和方法都拷贝一份到对象中了吗?对象本身都有这个方法了,为何还要把对象本身传给方法呢?

    首先,我们要知道Python的类其实也是一种对象,这种对象的属性不会改变,方法也一般不直接调用执行,这种对象只有一种作用——作为模板创建可运行的对象;其次,创建可运行的对象时,也不是一成不变的拷贝类对象,而是只生成类对象中的属性的引用,类对象中的方法实际上在可运行对象中是一个与类方法同名的方法指针;最后,当可运行对象调用方法时,实际是通过可运行对象中保持的方法指针执行类对象中的方法,但是类对象根本不知道有这样的可运行对象,所以系统必须要自动把可运行对象加到参数中。

    从上面的讨论中,容易理解——在类方法中,访问对象的属性和方法都要在它们之前加上"self."(例如:self.total += score)。

    下面是计算根号2的平方的程序:

>>> from math import sqrt

>>> sqrt(2) ** 2

2.0000000000000004

,显然不是整数2。如何才能得到整数2呢?

    首先我们想到先创建一个根号2的对象,然后调用对象自乘的方法:

打印的结果:

,显然是整数2了。

    这与前一例不同的地方:一是创建对象时有参数2(sr = SquareRoot(2));另外是类代码中有__init__方法(def __init__(self, intNum):)。不难理解,__init__方法就是接收创建对象时输入的参数,一般用于对象属性的初始化(self.intNum = intNum)。

    用print打印这个对象会怎样呢?

结果是:

,不是我想要的结果(根号2),怎么办呢?

    把程序改为下面的样子:

结果打印如下:

原来要在类中加一个__str__方法,print打印对象(sr)时,先把这个对象转化成字符串,首先检查对象(sr)有无__str__方法,如果有就打印这个方法的返回值,否则就是系统给的一个值。

    创建对象的模板——类真是个奇妙的东西,它有很多特性,不过封装性、继承性和多态性是类的三大特性

    所谓封装性,就是对象外只要知道接口,无需知道对象内的具体操作,就可以到达预期的结果。例如把二次根式转换为最简二次根式,把程序改成:

,运行结果:

。可见双下划线(__)开始,无下划线结束的方法时私有的方法,类外不可以访问。这样的属性又怎样呢?测试一下:

,结果打印:

。这种属性在类外也是不可见的。类可以有私密的属性和方法就是类的封装性

    所谓的继承性,就是一个类可以在其他类的基础上产生,继承了这些类的属性与方法。被继承的类叫父类,继承父类的类就是父类的子类。Python允许继承多个类,就是说多个父类的情况是允许的,但本教程为了简单起见,只讨论一个父类的情况。可以有多层次的继承关系,但Python中有一个金字塔顶的类,叫根类(Object),其他类都是从这个根类直接或间接继承而来的。下面用自然数、整数和有理数的四则运算封闭性说明类的继承关系。

程序代码:

;运行结果:

    继承的语法:

    访问父类同名方法的语法:

    类IntegralNumberClosure从父类NaturalNumberClosure中继承了属性closure1和closure2、方法__init__和getName;由于IntegralNumberClosure也有printClosures方法,所以父类NaturalNumberClosure中的printClosures方法被覆盖了,调用被覆盖的方法上面有描述。类RealNumberClosure继承了父类IntegralNumberClosure,IntegralNumberClosure从它的父类NaturalNumberClosure继承来的属性和方法可进一步被RealNumberClosure继承。也就是说,类可以继承上层次所有类的属性与方法,如果想覆盖,就用同名的属性和方法覆盖。

    类的私有属性和方法能不能继承呢?测试一下:

程序代码:

,结果:

;程序代码:

,结果:

。私密属性和方法都是不能继承的。 

    多态性Python中有两种表现:一、父类中同一个名称方法,在子类中通过方法的覆盖可以产生不同的结果;二、执行父类中一个方法,由于执行的子类不同而出现不同的结果。用交通灯来说明吧,交通灯有红、黄、绿不同的三种灯,亮灯操作虽然相同但灯的颜色不同。

    说明第一种多态性:

程序代码:

结果打印:

    说明第二种多态性:

程序代码:

结果打印:

    第一种多态性使程序更加复杂,一般情况是在第二种多态性无法解决问题的情况下才采用。

练习:

1、把本课的测试输入电脑自己测试一次。

2、利用列表和本课的二次方根化成最简二次方根的类,编写数值平方根式的加减运算程序。