第九章 类和对象
内容提要:
- 类是一种数据结构,可以包含数据成员和函数成员。
- 面向对象的程序设计具有三个基本特征:封装、继承、多态。
- 可以大大增加程序的可靠性、代码的可重用性和程序的可维护性,从而提高程序开发效率。
对象
- 对象的定义:
(1)从概念层面来讲,就是某种事物的抽象。抽象原则包括(1)数据抽象(2)过程抽象两个方面。
数据抽象:就是定义对象的属性
过程抽象:就是定义对象的操作
面向对象的程序设计强调把数据(属性)和操作(服务)结合为一个不可分的系统单位(即对象),对象的外部只需要知道它做什么,而不必知道它如何做。
(2)从规格层面讲,对象是一系列可以被其他对象使用的公共接口(对象交互)。
(3)从语言实现层面来看,对象封装了数据和代码(数据和程序)。
封装
封装,就是把客观事物抽象并封装成对象,即将数据成员、属性、方法和实践等集合在一个整体内。通过访问控制,还可以隐藏内部成员,只允许可行的对象访问或操作自己的部分数据或方法。
封装保证了对象的独立性,可以防止外部程序破坏对象的内部数据,同时便于程序的维护和修改。
继承
继承是面向对象的程序设计中代码重用的主要方法。继承是允许使用现有类的功能,并在无须重新改写原来的类的情况下,对这些功能进行扩展。继承可以避免代码复制和相关的代码维护等问题。
多态
子类具有所有父类的非私有数据的行为及子类自己定义的所有其他数据或行为。
即子类具有两个有效类型:子类的类型及其继承的父类的类型。
对象可以表示多个类型的能力称为多态性。
多态性允许每个对象以自己的方式去响应共同的消息,从而允许用户以更明确的方法建立通用软件,提高软件的可维护性。
类对象和实例对象
- 类是一个数据结构,类定义数据类型的数据(属性)和行为(方法)。对象是类的具体实体,也可以称为类的实例。
- 在python语言中,类称为类对象;类的实例称为实例对象。
类对象:
例: class Person1: #定义类Person1 pass #类体为空语句 p1=Person1() #创建和使用对象 print(p1)
实例对象:
类是抽象的,要使用类定义的功能,就必须实例化,即创建类的对象。创建对象后可以使用“."运算符来调用其成员。
创建类的对象 = 创建类的实例 = 实例化类 ==都说明以类为模板生成了一个对象的操作。
c1=complex(1,2)#创建类complex的实例对象并绑定到变量c1 c1.conjugate()#调用c1的conjugate()方法,返回其共轭值 c1.real
属性
属性:类的数据成员实在类中定义的成员变量(域),用来存储描述类的特征的值,称为属性。
属性可以被该类中定义的方法访问,也可以通过类对象或实例对象进行访问。(而在函数体或代码块中定义的局部变量,只能在其定义的范围内进行访问)
属性实际上是在类中的变量。
实例属性:
通过 "self.变量名" 定义的属性,称为实例属性,也称为实例变量。
类的每个实例都包含该类的实例变量的一个单独副本。
实例变量在类的内部通过self访问,在外部通过对象实例访问。
class Person2: #定义类Person2 def __init__(self,name,age): #__init__方法(构造函数) self.name = name #初始化self.name,即成员变量name(域) self.age = age #初始化self.age,即成员变量age(域) def say_hello(self): #定义类Person2的函数say_hello print("您好,我叫",self.name) #在实例方法中通过self.name读取成员变量name p1 = Person2('张三',25) #创建对象 p1.say_hello() #调用对象的方法 print(p1.age)
类属性
python也允许声明属于类对象本身的变量,即类属性,也称为类变量、静态属性。
类属性属于整个类,不是特定实例的一部分,而是所有实例之间共享的一个副本。
class Person3: count = 0 #定义类属性count name = 'Person' #定义类属性name Person3.count += 1 print(Person3.count)#类名访问,读取并显示类属性count print(Person3.name)#类名访问,读取并显示类属性name p1 = Person3() #创建实例对象p1 p2 = Person3() #创建实例对象p2 print((p1.name, p2.name)) #通过实例对象访问,读取成员变量的值 Person3.name = '雇员' #通过类名访问,设置类属性值 print((p1.name, p2.name)) #读取成员变量的值 p1.name = '员工' #通过实例对象访问,设置实例对象成员变量的值 print((p1.name, p2.name))
运行结果:
1 Person ('Person', 'Person') ('雇员', '雇员') ('员工', '雇员')
私有属性和公有属性:
约定以两个下划线开头,但是不以两个下划线结束的属性是私有的,其他为公共的,不能直接访问私有属性,但可以在方法中访问。
class A: __name = 'classA' #定义的__name为私有属性 def get_name(): print(A.__name) #在类方法中访问私有类属性 A.get_name() A.__name #不能直接访问私有类属性
@property装饰器
面向对象编程的封装性原则要求不直接访问类中的数据成员。python中可以通过定义私有属性,然后定义相应的访问该私有属性的函数,并使用@property装饰器装饰这些函数。程序可以把函数当作属性访问,从而提供更友好的访问方式。
class Person11: def __init__(self,name): self.__name = name @property def name(self): """i'm the 'x' property.""" return self.__name p = Person11("王五") print(p.name)
自定义属性
对象通过特殊属性__dict__存储自定义属性,即类定义中不存在的属性。
>>>class C1: pass >>>o = C1 >>>o.name='custom name' >>>o.name >>>o.__dict__
方法
实例方法:
方法是与类相关的函数
- 一般情况下,类方法的第一个参数一般为self,这种方法称为实例方法。实例方法对类的某个给定的实例进行操作,可以通过self显式地访问该实例。
虽然类方法地第一个参数为self,但调用时,用户不需要也不能给self传值,事实上,python自动把对象实例传给self。
例:假设声明了一个类myclass和类方法my_func(self, p1, p2)
obj1 = myclass() #创建myclass的一个实例obj1
obj1.my_func(p1,p2) #实例对象obj1调用myclass的类方法my_func
python将会自动将语句:obj1.my_func(p1,p2) 转换为 :obj1.my_func(obj1, p1, p2),即自动把对象实例obj1传给self
静态方法
类方法
构造函数__init__
__init__方法即构造函数(构造方法),用于执行类的实例的初始工作。创建完对象后调用,初始化当前对象的实例,无返回值。
析构函数__del__
__del__方法即析构函数,用于实现销毁类的实例所需的操作,如释放对象占用的非托管资源(例如:打开的文件、网络连接等)
默认情况下,当对象不再使用时,执行__del__方法。
class Person3: count = 0#定义类变量count def __init__(self, name, age):#构造函数 self.name = name self.age = age Person3.count += 1 #创建一个实例时,计数加1 def __del__(self): #析构函数 Person3.count -= 1 #删除一个实例时,计数减1 def say_hi(self): #定义类方法 print("您好,我叫",self.name) def get_count(self): print("总计数为:",Person3.count) print("总计数为:",Person3.count) #类名访问 P31 =Person3('张三',25) #创建实例化对象 P31.say_hi() #调用对象的方法 Person3.get_count(Person3)# p31 = Person3('李四',28) p31.say_hi() Person3.get_count(Person3) del P31 #删除对象 p31.get_count() del p31 Person3.get_count(Person3)
运行结果:
总计数为: 0 您好,我叫 张三 总计数为: 1 您好,我叫 李四 总计数为: 2 总计数为: 1 总计数为: 0
私有方法与公有方法:
与私有属性类似,python约定两个下划线开头,但不以两个下划线结束的方法是私有方法(private),其他为公有方法(public)
不能直接访问私有方法,但可以在其他方法中访问。
class book: def __init__(self, name, author, price): self.name = name self.author = author self.price = price def __check_name(self): #定义私有方法,用来判断name是否为空 if self.name =='': return False else: return True def get_name(self): #定义类方法,在该方法中访问了私有方法 if self.__check_name():print(self.name,self.author) else:print('no value') b = book('python程序设计教程','江红',2.0) #创建了一个对象 b.get_name() #对象调用类方法 b.__check_name() #对象直接调用私有方法,(会出错)
运行结果
python程序设计教程 江红 Traceback (most recent call last): File "C:/Users/yangx/PycharmProjects/day1/book/part9.py", line 77, in <module> b.__check_name() #对象直接调用私有方法,导致出错 AttributeError: 'book' object has no attribute '__check_name'
方法重载
在其他程序设计语言中,方法可以重载:即定义多个重名方法,只要保证方法签名是唯一的。(方法签名包括三个部分:方法名、参数数量和参数类型)
python语言是动态语言,参数数量可由可选参数和可变参数来控制,所以python对象方法不需要重载。
继承
派生类(子类)
- python支持多重继承,即一个派生类(子类)可以继承多个基类(父类)
- 如果在定义中没有指定基类,则默认其基类为object。object是所有对象的跟基类,定义了公有方法的默认实现
- 声明派生类(子类)时,必须在其构造函数中调用基类的构造函数(调用时注意基类的参数写法)
class Person: #基类(父类) def __init__(self, name, age): self.name = name self.age = age def say_hi(self): #定义基类的方法 print('您好,我叫{0},今年{1}岁了'.format(self.name, self.age)) class student(Person): #派生类(子类) def __init__(self, name, age, stu_id): #派生类的构造函数 Person.__init__(self, name, age) #调用基类的构造函数 self.stu_id = stu_id def say_hi(self): #定义派生类的方法 Person.say_hi(self) print('我是学生,我的学号是{0}'.format(self.stu_id)) p1= Person('张网易',33) p1.say_hi() s1 = student('李遥儿',17,'12333382988') s1.say_hi()
结果:
您好,我叫张网易,今年33岁了
您好,我叫李遥儿,今年17岁了
我是学生,我的学号是12333382988
类成员的继承与重写
通过继承,派生类继承基类中除构造方法之外的所有成员。如果在派生类中重新定义从基类继承的方法,则派生类中定义的方法覆盖从基类中继承的方法。
import math class dimension: def __init__(self, x, y): self.x = x self.y = y def area(self): #基类的方法area() pass class circle(dimension): def __init__(self,r): dimension.__init__(self, r, 0)#注意基类的构造函数的参数形式 def area(self): #派生类重写了基类的方法area() S1 = math.pi*(int(self.x) ** 2) return S1 class rectangle(dimension): def __init__(self,w,h): dimension.__init__(self, w, h) #注意基类的构造函数的参数形式 def area(self): #派生类重写了基类的方法area() S2 = (int(self.x) * int(self.y))/2 return S2 d1=circle(2.0) d2 = rectangle(2.0,4.0) print(d1.area(),d2.area())
对象的特殊方法:
对象的浅拷贝和深拷贝
浅拷贝:对象的赋值引用同一个对象,即不拷贝对象。若要拷贝对象,使用如下方法:
(1)切片操作:acc1[:]
(2)对象实例化:list(acc1)
(3)copy模块的copy函数:copy.copy(acc1)
深拷贝:使用copy.deepcopy函数
上机实践
29:编写程序,计算圆的周长、面积和球的表面积、体积
import math class MyMath: def __init__(self,x): self.x = x # self.y = y def area(self): pass def Length(self): pass def vact(self): pass class circle(MyMath): def __init__(self,r): MyMath.__init__(self,r) def area(self): Area = math.pi * (self.x ** 2) print("圆的面积 = {0:1.2f}".format(Area) ) return Area def Length(self): lengths = math.pi * (self.x * 2) print("圆的周长 = {0:1.2f}".format(lengths)) return lengths class ball(MyMath): def __init__(self,r): MyMath.__init__(self,r) def area(self): Area = 4 * math.pi * (self.x ** 2) print("球的表面积 = {0:1.2f}".format(Area)) return Area def vact(self): V = (4/3) * math.pi * (self.x ** 3) print("球的体积 = {0:1.2f}".format(V)) return V #测试代码 r = int(input("请输入半径:")) circle1 = circle(r) circle1.Length() circle1.area() ball1 = ball(r) ball1.area() ball1.vact()
30 编写程序,实现华氏温度和摄氏温度的转换
class Temperature: def __init__(self,number): self.number = number def ToFahrenheit(self): fahrenhiet = self.number + 56 print("摄氏温度 = {0:1.1f},华氏温度 = {1:1.1f}".format(self.number,fahrenhiet)) def ToCelsius(self): celsius = self.number - 56 print("华氏温度 = {0:1.1f},摄氏温度 = {1:1.1f}".format(self.number,celsius)) #测试代码 T1 = int(input("请输入摄氏温度:")) temperture1 = Temperature(T1) temperture1.ToFahrenheit() T2 = int(input("请输入华氏温度:")) temperture2 = Temperature(T2) temperture2.ToCelsius()