1、 Python语言程序设计语言程序设计刘刘卫国卫国第第9章章 面向对象程序设计面向对象程序设计 配配源代码源代码9.1.1 面向对象的基本概念面向对象的基本概念1对象对象现实现实世界中客观存在的事物称作对象世界中客观存在的事物称作对象(object),任何对象都具有各自的特征(属性),任何对象都具有各自的特征(属性)和行为(方法)。和行为(方法)。面向对象程序设计面向对象程序设计中的对象是现实世界中的中的对象是现实世界中的客观事物在程序设计中的具体体现,它也具有自客观事物在程序设计中的具体体现,它也具有自己的特征和行为。对象的特征用数据来表示,称己的特征和行为。对象的特征用数据来表示,称为属性
2、(为属性(property)。对象的行为用)。对象的行为用程序代码来程序代码来实现,称为对象的方法(实现,称为对象的方法(method)。)。总之,任总之,任何对象都是由属性和方法组成的。何对象都是由属性和方法组成的。9.1 面向对象程序设计概述面向对象程序设计概述2类类类类(class)是具有相同属性和行为的一组对)是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一象的集合,它为属于该类的全部对象提供了统一的抽象描述。任何对象都是某个类的实例的抽象描述。任何对象都是某个类的实例(instance)。)。在在系统中通常有很多相似的对象,它们具有系统中通常有很多相似的对象,它
3、们具有相同名称和类型的属性、响应相同的消息、使用相同名称和类型的属性、响应相同的消息、使用相同的方法。将相似的对象分组形成一个类,每相同的方法。将相似的对象分组形成一个类,每个这样的对象被称为类的一个实例,个这样的对象被称为类的一个实例,一个类中的一个类中的所有对象共享一个公共的定义,尽管所有对象共享一个公共的定义,尽管它们对属性它们对属性所赋予的值不同。所赋予的值不同。3消息消息一个系统由若干个对象组成,各个对象之间一个系统由若干个对象组成,各个对象之间通过消息(通过消息(message)相互联系、相互作用。)相互联系、相互作用。消息是一个对象要求另一个对象实施某项操消息是一个对象要求另一个
4、对象实施某项操作的请求。发送者发送消息,在一条消息中,作的请求。发送者发送消息,在一条消息中,需要包含消息的接收者和要求接收者执行某需要包含消息的接收者和要求接收者执行某项操作的请求,接收者通过调用相应的方法项操作的请求,接收者通过调用相应的方法响应消息,这个过程被不断地重复,从而驱响应消息,这个过程被不断地重复,从而驱动整个程序的运行。动整个程序的运行。4封装封装封装封装(encapsulation)是指把对象的数)是指把对象的数据(属性)和操作数据的过程(方法)结合在据(属性)和操作数据的过程(方法)结合在一起,构成独立的单元,它的内部信息对外界一起,构成独立的单元,它的内部信息对外界是隐
5、蔽的,不允许外界直接存取对象的属性,是隐蔽的,不允许外界直接存取对象的属性,只能通过使用类提供的外部接口对该对象实施只能通过使用类提供的外部接口对该对象实施各项操作,保证了程序中数据的安全性。各项操作,保证了程序中数据的安全性。类类是数据封装的工具,对象是封装的实现。是数据封装的工具,对象是封装的实现。类的访问控制机制体现在类的成员中可以有公类的访问控制机制体现在类的成员中可以有公有成员、私有成员和保护成员。对于有成员、私有成员和保护成员。对于外界而言,外界而言,只需要知道对象所表现的外部行为,只需要知道对象所表现的外部行为,而不必了而不必了解解内部实现细节。内部实现细节。5继承继承继承继承(
6、inheritance)反映的是类与类之间)反映的是类与类之间抽象级别的不同,根据继承与被继承的关系,抽象级别的不同,根据继承与被继承的关系,可分为基类和衍类,基类也称为父类,衍类也可分为基类和衍类,基类也称为父类,衍类也称为子类,正如称为子类,正如“继承继承”这个词的字面含义一这个词的字面含义一样,子类将从父类那里获得所有的属性和方法,样,子类将从父类那里获得所有的属性和方法,并且可以对这些获得的属性和方法加以改造,并且可以对这些获得的属性和方法加以改造,使之具有自己的特点。使之具有自己的特点。6多态多态多态多态(polymorphism)是指同一名字的)是指同一名字的方法产生了多个不同的动
7、作行为,也就是不同方法产生了多个不同的动作行为,也就是不同的对象收到相同的消息时产生不同的行为方式。的对象收到相同的消息时产生不同的行为方式。将将多态的概念应用于面向对象程序设计,多态的概念应用于面向对象程序设计,增强了程序对客观世界的模拟性,使得对象程增强了程序对客观世界的模拟性,使得对象程序具有了更好的可读性,更易于理解,而且显序具有了更好的可读性,更易于理解,而且显著提高了软件的可复用性和可扩充性。著提高了软件的可复用性和可扩充性。9.1.2 从面向过程到面向对象从面向过程到面向对象面向过程面向过程程序设计就是采用自顶向下的方程序设计就是采用自顶向下的方法,分析出解决问题所需要的步骤,将
8、程序分法,分析出解决问题所需要的步骤,将程序分解为若干个功能模块,每个功能模块用函数来解为若干个功能模块,每个功能模块用函数来实现。实现。一一个面向对象的程序一般由类的声明和类个面向对象的程序一般由类的声明和类的使用两部分组成。程序设计始终围绕的使用两部分组成。程序设计始终围绕“类类”展开。通过声明类,构建了程序所要完成的功展开。通过声明类,构建了程序所要完成的功能,体现了面向对象程序设计的思想。在能,体现了面向对象程序设计的思想。在Python中,所有数据类型都可以视为对象中,所有数据类型都可以视为对象,当,当然也可以自定义对象。自定义的对象然也可以自定义对象。自定义的对象数据类型数据类型就
9、是面向对象中的类的概念。就是面向对象中的类的概念。9.2.1 类的定义类的定义在在Python中,通过中,通过class关键字来定义类。关键字来定义类。定义类的一般格式如下:定义类的一般格式如下:class 类名类名:类体类体例如定义了一个例如定义了一个Person类:类:class Person:name=brenden#定义了一个属性定义了一个属性 def printName(self):#定义了一个方法定义了一个方法 print(self.name)9.2 类与对象类与对象9.2.2 对象的创建和使用对象的创建和使用在在Python中,用赋值的方式创建类的实例,中,用赋值的方式创建类的实
10、例,一般格式为一般格式为:对象对象名名=类名类名(参数列表参数列表)创建创建对象后,可以使用对象后,可以使用“.”运算符,通过运算符,通过实例对象来访问这个类的属性和方法(函数),实例对象来访问这个类的属性和方法(函数),一般格式为:一般格式为:对象对象名名.属性名属性名对象对象名名.函数名函数名()例如例如,语句语句“p=Person()”产生了一个产生了一个Person的的实例对象,此时可以用实例对象,此时可以用p.name来调来调用用类的类的name属性。属性。例例9-1 类和对象应用示例。类和对象应用示例。程序如下:程序如下:class CC:x=10#定义属性定义属性 y=20#定义
11、属性定义属性 z=30#定义属性定义属性 def show(self):#定义方法定义方法 print(self.x+self.y+self.z)/3)b=CC()#创建实例对象创建实例对象bb.x=30#调用属性调用属性xb.show()#调用方法调用方法show程序输出结果如下程序输出结果如下:26.6666666666666689.3.1 属性和方法的访问控制属性和方法的访问控制1属性的访问控制属性的访问控制在在Python中没有像中没有像C+中中public和和private这些关键字来区别公有属性和私有属性,它是这些关键字来区别公有属性和私有属性,它是以属性命名方式来区分,如果在属性
12、名前面加以属性命名方式来区分,如果在属性名前面加了两个下划线了两个下划线“_”,则表明该属性是私有属,则表明该属性是私有属性,否则为公有属性。方法也一样,如果在方性,否则为公有属性。方法也一样,如果在方法名前面加了法名前面加了2个下划线,则表示该方法是个下划线,则表示该方法是私私有的有的,否则为公有的。,否则为公有的。9.3 属性和方法属性和方法例如:例如:class Person:name=brenden age=18p=Person()print(p.name,p.age)这里这里的的name和和age都是公有的,可以直接都是公有的,可以直接在类外通过对象名访问,如果想定义成私有的,在类外
13、通过对象名访问,如果想定义成私有的,则需在前面加则需在前面加2个下划线个下划线“_”。2方法的访问控制方法的访问控制在在类中可以根据需要定义一些方法,定义类中可以根据需要定义一些方法,定义方法采用方法采用def关键字,在类中定义的方法至少关键字,在类中定义的方法至少会有一个参数,一般以名为会有一个参数,一般以名为“self”的变量的变量作为该参数(用其他名称也可以),而且需要作为该参数(用其他名称也可以),而且需要作为第一个参数。下面看一个例子。作为第一个参数。下面看一个例子。例例9-2 方法的访问控制使用示例。方法的访问控制使用示例。程序如下:程序如下:class Person:_name=
14、brenden _age=18 def getName(self):return self._name def getAge(self):return self._agep=Person()print(p.getName(),p.getAge()程序输出结果如下程序输出结果如下:brenden 189.3.2 类属性和实例属性类属性和实例属性1类属性类属性顾名思义顾名思义,类属性(,类属性(class attribute)就是就是类对象所拥有的属性,它被所有类对象的实例类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本。对于对象所共有,在内存中只存在一个副本。对于公有的
15、类属性,在类外可以通过类对象和实例公有的类属性,在类外可以通过类对象和实例对象访问。对象访问。例如:例如:class Person:name=brenden#公有的类属性公有的类属性 _age=18#私有的类属性私有的类属性p=Person()print(p.name)#正确,但不提倡正确,但不提倡print(Person.name)#正确正确print(p._age)#错误,不能在类外通过实错误,不能在类外通过实例对象访问私有的类属性例对象访问私有的类属性print(Person._age)#错误,不能在错误,不能在类外通类外通过过类对象访问私有的类属性类对象访问私有的类属性2实例属性实例属
16、性实例实例属性(属性(instance attribute)是不需要是不需要在类中显式定义,而在在类中显式定义,而在_init_构造函数中定构造函数中定义的,定义时以义的,定义时以self作为前缀。在其他方法中作为前缀。在其他方法中也可以随意添加新的实例属性也可以随意添加新的实例属性,但并不提倡这但并不提倡这么做,所有的实例属性最好在么做,所有的实例属性最好在_init_中给出。中给出。实例属性属于实例(对象),只能通过对象名实例属性属于实例(对象),只能通过对象名访问。访问。例如:例如:class Car:def _init_(self,c):self.color=c#定义实例对象属性定义实
17、例对象属性 def fun(self):self.length=1.83#给实例添加属性给实例添加属性,但但不提倡不提倡 s=Car(Red)print(s.color)Red s.fun()print(s.length)1.839.3.3 类的方法类的方法1类中类中类类中最常用的内置方法就是构造方法和析中最常用的内置方法就是构造方法和析构方法。构方法。(1)构造方法)构造方法构造构造方法方法_init_(self,)在生成对象时调在生成对象时调用,可以用来进行一些属性初始化操作,不需用,可以用来进行一些属性初始化操作,不需要显式去调用,系统会默认去执行。构造方法要显式去调用,系统会默认去执行
18、。构造方法支持重载,如果用户自己没有重新定义构造方支持重载,如果用户自己没有重新定义构造方法,系统就自动执行默认的构造方法。看下面法,系统就自动执行默认的构造方法。看下面的程序。的程序。例例9-3 构造方法使用示例。构造方法使用示例。程序如下:程序如下:class Person:def _init_(self,name):self.PersonName=name def sayHi(self):print(Hello,my name is.format(self.PersonName)p=Person(Jasmine)p.sayHi()程序输出结果如下:程序输出结果如下:Hello,my na
19、me is Jasmine.(2)析构方法)析构方法析析构方法构方法_del_(self)在释放对象时调用,在释放对象时调用,支持重载,可以在其中进行一些释放资源的操支持重载,可以在其中进行一些释放资源的操作,不需要显式调用。下面的例子说明了类的作,不需要显式调用。下面的例子说明了类的普通成员函数以及构造方法和析构方法的作用普通成员函数以及构造方法和析构方法的作用。class Test:def _init_(self):print(AAAAA)def _del_(self):print(BBBBB)def myf(self):print(CCCCC)obj=Test()AAAAA obj.my
20、f()CCCCC del objBBBBB2类方法、实例方法和静态方法类方法、实例方法和静态方法(1)类方法)类方法类类方法是类对象所拥有的方法,需要用修方法是类对象所拥有的方法,需要用修饰器饰器“classmethod”来标识其为类方法,来标识其为类方法,对于类方法,第一个参数必须是类对象,一般对于类方法,第一个参数必须是类对象,一般以以“cls”作为第一个参数。当然可以用其他名作为第一个参数。当然可以用其他名称的变量作为其第一个参数,但是大都习惯以称的变量作为其第一个参数,但是大都习惯以“cls”作为第一个参数的名字,所以一般用作为第一个参数的名字,所以一般用“cls”。能够通过实例对象和
21、类对象去访问类。能够通过实例对象和类对象去访问类方法。方法。例如:例如:class Person:place=Changsha classmethod#类方法,用类方法,用classmethod来进行修饰来进行修饰 def getPlace(cls):return cls.place p=Person()print(p.getPlace()#可以用过实例可以用过实例对象引用对象引用Changsha print(Person.getPlace()#可以通过可以通过类类对象对象引用引用Changsha类类方法还有一个用途就是可以对类属性进方法还有一个用途就是可以对类属性进行修改。例如:行修改。例如
22、:class Person:place=Changsha classmethod def getPlace(cls):return cls.place classmethod def setPlace(cls,place1):cls.place=place1 p=Person()p.setPlace(Shanghai)#修改类属性修改类属性 print(p.getPlace()Shanghai print(Person.getPlace()Shanghai(2)实例方法)实例方法实例实例方法是类中最常定义的成员方法,它方法是类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第至少
23、有一个参数并且必须以实例对象作为其第一个参数,一般以名为一个参数,一般以名为self的变量作为第一个的变量作为第一个参数,当然可以以其他名称的变量作为第一个参数,当然可以以其他名称的变量作为第一个参数。在类外实例方法只能通过实例对象去调参数。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用。用,不能通过其他方式去调用。例如:例如:class Person:place=Changsha def getPlace(self):#实例方法实例方法 return self.place p=Person()print(p.getPlace()#正确,可以用过正确,可以用过实例对象引用实例对象
24、引用Changsha print(Person.getPlace()#错误,错误,不能通不能通过类过类对象引用实例方法对象引用实例方法(3)静态方法)静态方法静态静态方法需要通过修饰器方法需要通过修饰器“staticmethod”来进行修饰,静态方法不来进行修饰,静态方法不需要多定义参数。例如:需要多定义参数。例如:class Person:place=Changsha staticmethod def getPlace():#静态方法静态方法 return Person.place print(Person.getPlace()Changsha9.4.1 继承继承在在Python中,类继承的
25、定义形式如下:中,类继承的定义形式如下:class 子类名子类名(父类名父类名):类类体体在在定义一个类的时候,可以在类名后面紧定义一个类的时候,可以在类名后面紧跟一对括号,在括号中指定所继承的父类,如跟一对括号,在括号中指定所继承的父类,如果有多个父类,多个父类名之间用逗号隔开果有多个父类,多个父类名之间用逗号隔开。9.4 继承和多态继承和多态例例9-4 以大学里的学生和教师为例,可以以大学里的学生和教师为例,可以定义一个父类定义一个父类UniversityMember,然后类,然后类Student和类和类Teacher分别继承类分别继承类UniversityMember。9.4.2 多重继
26、承多重继承多重多重继承的定义形式是:继承的定义形式是:class 子类名子类名(父类名父类名1,父类名父类名2,):类类体体例例9-5 多重继承程序示例。多重继承程序示例。程序如下:程序如下:class A():def foo1(self):print(AAAAA)class B(A):def foo2(self):print(BBBBB)class C(A):def foo1(self):print(CCCCC)class D(B,C):passd=D()d.foo1()程序在程序在Python 3.x环境下的输出结果是环境下的输出结果是:CCCCC程序在程序在Python 2.x环境下的输
27、出结果是环境下的输出结果是:AAAAA9.4.3 多态多态多态多态性多态即多种形态,是指不同的对象性多态即多种形态,是指不同的对象收到同一种消息时会产生不同的行为。在程序收到同一种消息时会产生不同的行为。在程序中消息就是调用函数,不同的行为就是指不同中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数。的实现方法,即执行不同的函数。在在Python中很多地方都可以体现多态的特中很多地方都可以体现多态的特性,例如内置函数性,例如内置函数len(),len函数不仅可以计算函数不仅可以计算字符串的长度,还可以计算列表、元组等对象字符串的长度,还可以计算列表、元组等对象中的数据个数,这
28、里在运行时通过参数中的数据个数,这里在运行时通过参数类型确类型确定其具体的计算过程,正是多态的定其具体的计算过程,正是多态的一种体现。一种体现。例例9-7 已知已知y,当当f(n)122334n(n1)时,求时,求y的值。的值。分析分析:为了说明面向过程程序设计和面向对象:为了说明面向过程程序设计和面向对象程序设计的区别,分别用面向过程方法和面向程序设计的区别,分别用面向过程方法和面向对象方法来写程序。对象方法来写程序。9.5 面向对象程序设计应用举例面向对象程序设计应用举例例例9-8 用面向对象方法编写例用面向对象方法编写例4-11对应的对应的程序。程序。例例9-9 某商店销售某一商品,允许
29、销售人员在某商店销售某一商品,允许销售人员在一定范围内灵活掌握售价(一定范围内灵活掌握售价(price),现已知当),现已知当天天3名销货员的销售情况为:名销货员的销售情况为:销货员号(销货员号(num)销货件数(销货件数(quantity)销货单价(销货单价(price)101 5 23.5102 12 24.56103 100 21.5编写程序,计算当日此商品的总销售款编写程序,计算当日此商品的总销售款sum以以及每件商品的平均售价。及每件商品的平均售价。分析:利用字典来组织数据,以销货分析:利用字典来组织数据,以销货员号作为员号作为字典关键字,通过关键字遍历字典。字典关键字,通过关键字遍
30、历字典。配套源代码例9-1 程序如下:class CC:x=10#定义属性 y=20#定义属性 z=30#定义属性 def show(self):#定义方法 print(self.x+self.y+self.z)/3)b=CC()#创建实例对象bb.x=30#调用属性xb.show()#调用方法show配套源代码例9-2 程序如下:class Person:_name=brenden _age=18 def getName(self):return self._name def getAge(self):return self._agep=Person()print(p.getName(),p
31、.getAge()配套源代码例9-3 程序如下:class Person:def _init_(self,name):self.PersonName=name def sayHi(self):print(Hello,my name is.format(self.PersonName)p=Person(Jasmine)p.sayHi()例9-4 程序如下:class UniversityMember:#定义父类 def _init_(self,name,age):self.name=name self.age=age print(init UniversityMember:,self.name)
32、def tell(self):print(name:;age:.format(self.name,self.age)class Student(UniversityMember):#定义子类Student def _init_(self,name,age,marks):UniversityMember._init_(self,name,age)self.marks=marks print(init Student:,self.name)def tell(self):UniversityMember.tell(self)print(marks:,self.marks)class Teacher(
33、UniversityMember):#定义子类Teacher def _init_(self,name,age,salary):UniversityMember._init_(self,name,age)#显式调用父类构造方法 self.salary=salary print(init Teacher:,self.name)def tell(self):UniversityMember.tell(self)print(salary:,self.salary)s=Student(Brenden,18,92)t=Teacher(Jasmine,28,2450)members=s,t printfo
34、r member in members:member.tell()配套源代码例9-5 程序如下:class A():def foo1(self):print(AAAAA)class B(A):def foo2(self):print(BBBBB)class C(A):def foo1(self):print(CCCCC)class D(B,C):passd=D()d.foo1()例9-6 程序如下:class base(object):def _init_(self,name):self.name=name def show(self):print(base class:,self.name)
35、class subclass1(base):def show(self):print(sub class 1:,self.name)class subclass2(base):def show(self):print(sub class 2:,self.name)class subclass3(base):pass def testFunc(o):o.show()first=subclass1(1)second=subclass2(2)third=subclass3(3)lst=first,second,thirdfor p in lst:testFunc(p)例9-7 面向过程方法的程序如下
36、:def f(n):#定义求f(n)的函数 s=0 for i in range(1,n+1):s+=i*(i+1)return sy=f(40)/(f(30)+f(20)print(y=,y)面向对象方法的程序如下:class calculate:def _init_(self,n):self.n=n def f(self):#求f(n)的成员函数 s=0 for i in range(1,self.n+1):s+=i*(i+1)return sob1=calculate(40)ob2=calculate(30)ob3=calculate(20)y=ob1.f()/(ob2.f()+ob3.
37、f()print(y=,y)例9-8 程序如下:class compute:def yn(self):#计算y和n的函数 self.n=1 self.y=0.0 while self.y3.0:self.f=1.0/(2*self.n-1)self.y+=self.f self.n+=1 self.y=self.y-self.f self.n=self.n-2 def print(self):#输出结果的函数 print(y=0,n=1.format(self.y,self.n)def main():#主函数 obj=compute()obj.yn()obj.print()main()例9-9
38、 程序如下:class Product:def total(self):prod=101:5,23.5,102:12,24.56,103:100,21.5 self.sum=0.0 self.n=0 for key in prod.keys():quantity=prodkey0 price=prodkey1 self.sum+=quantity*price self.n+=quantity def display(self):print(self.sum)print(self.sum/self.n)def main():ob=Product()ob.total()ob.display()main()