【Python】类与对象(下)
在类与对象(上)中我们介绍了类的定义语法,类对象与实例对象、类方法与实例方法、构造器与析构器以及最后的静态方法。在这篇中,我们将会介绍类与对象的一些高级语法特性,它们也是其他各种支持面向对象的高级语言中普遍存在的语法特性,包括但不限于:运算符重载、继承以及多态。
运算符重载.
最初接触到运算符重载是在C++中,Java并不支持运算符重载,而是通过一些成员方法来完成某些功能。Python中也支持运算符重载,并且语法也并不是很复杂,Python对于每一种运算,都指定了在重载该运算符时需要定义的方法。也就是说,Python把运算符和类的实例方法绑定起来,每个运算符都对应一个方法,运算符重载就是让类的实例对象可以参与内置类型的计算。我们下面给出部分运算符重载方法的对应关系:
1.基本运算重载.
下面我们给出一段代码,指出一些运算符重载时的细节:
class MyComplex:
def __init__(self,real=0,image=0):
self.real=real
self.image=image
def __add__(self,other):
return MyComplex(self.real+other.real,self.image+other.image)
def __sub__(self,other):
return MyComplex(self.real-other.real,self.image-other.image)
def __mul__(self,other):
return MyComplex(self.real*other.real-self.image*other.image,
self.image*other.real+self.real*other.image)
def __truediv__(self,other):
model=other.real**2+other.image**2
return MyComplex((self.real*other.real+self.image*other.image)/model,
(self.image*other.real-self.real*other.image)/model)
def show(self):
if self.image<0:
print('(',self.real,self.image,'j)')
else:
print('(',self.real,'+',self.image,'j)')
com1=MyComplex(3,6)
com2=MyComplex(7,8)
(com1+com2).show()
(com1-com2).show()
(com1*com2).show()
(com1/com2).show()
这是一个复数类的定义,我们定义了__add__()、sub()等方法,从而使得MyComplex能够进行像int类型那样的加减乘除等运算。
2.重载解释为字符串.
当对象作为print()或str()等函数的参数时,会自动调用该对象的__str__()方法返回该对象的字符串表示形式。我们在class MyComplex的定义中加入下面这一段代码:
def __str__(self):
if self.image<0:
return '('+str(self.real)+str(self.image)+'j)'
else:
return '('+str(self.real)+'+'+str(self.image)+'j)'
然后再主程序中加入两句print语句
print(com1)
print(com2)
输出结果为
其实MyComplex中__str__()的实现也已经运用了运算符重载(疯狂递归)。因为Python内置的str类型已经实现了+的重载作为字符串的连接操作,并且int类型也已经实现了作为str()的参数时会调用的__str__()方法的实现,这些都是Python语言已经为我们做好的工作,我们可以在这基础之上,实现符合我们需求的多样化功能。
3.索引及切片重载.
当实例对象执行索引、切片或for迭代时,该对象会调用重载的__getitem__()方法,我们给出示例如下:
class Data:
def __init__(self,list):
self.data=list[:]
def __getitem__(self,index):
print("__getitem__ called.")
return self.data[index]
data=Data([1,2,3,4,5])
print(data[0])
print(data[1:3])
print("___________________")
for i in data:
print(i,end=" ")``
可以看到,索引、切片以及for迭代语句都会自动调用__getitem__()方法。值得注意的是,在for语句最后一次判断循环条件是否还成立时,它也会调用一次__getitem__()方法,只不过在发现元素已经耗尽时,无法返回一个元素值以执行for语句的循环体了,从而导致退出。
另外,我们在通过赋值语句给索引、切片进行赋值时,将会自动调用__setitem__()方法,代码展示如下:
class Data:
def __init__(self,list):
self.data=list[:]
def __getitem__(self,index):
return self.data[index]
def __setitem__(self,key,value):
print("__setitem__ called.")
self.data[key]=value
data=Data([1,2,3,4,5])
data[0]=99
print(*data)
data[1:3]=48,72
print(*data)
可以看出,无论是索引对单个元素的赋值,还是切片操作对于多个元素的赋值,都只会调用一次__setitem__()方法。
面向对象三大特征.
面向对象程序设计实际上就是对现实世界的对象进行建模操作。面向对象程序设计的特征可以概括为:封装、继承与多态。
封装是面向对象程序设计的核心思想,它指将对象的属性和行为封装起来,而封装的载体就是类class。类通常对客户隐藏实现细节体现的就是封装的思想。例如,计算机的主机是由内存条、硬盘以及风扇等小部件组成的,生产厂家把这些部件用一个外壳封装起来组成主机。而用户使用计算机时,并不需要关心主机内部的组成以及工作原理。
继承是面向对象程序设计提高重用性的重要措施,它体现了特殊类与一般类之间的关系。特殊类继承一般类之后,特殊类包含了一般类所有的属性和行为,并且特殊类还可以添加属于自己的行为、属性。
程序中的多态指的是同一种行为对应着多种不同的实现。多态可以分为静态多态和动态多态。静态多态是指在编译期间就可以确定下来的多态性为,例如重载函数的选择(虽然Python不支持函数重载,但我们要陈述这样的思想);而动态多态则是在运行时才确定的行为,例如我们有一个shape类,在shape中我们定义了求面积的行为,这一行为在shape类中并不具有任何意义,因为shape并不是具体的图形;又定义了一些类例如三角形类、圆类以及矩形类继承shape类,这些代表着具体图形的类都继承了shape求面积的行为,从而我们可以重写(overwrite)求面积方法,使用不同的面积公式求出不同图形的面积。而多态就在于我们在使用shape类的实例对象引用其子类例如三角形类的实例对象时,通过shape类实例对象能够调用子类的求面积方法,从而可以实现一个方法,多种版本的功能。
1.封装.
封装通过隐藏类的实现细节,而留下一些定义清晰的接口,迫使用户只能通过接口来“规范地”访问数据,从而在一定程度上避免了恶意破坏数据的可能,提升了程序的安全性。为了让使用者不能随意地修改实例对象的某些重要属性时,可以将该属性声明为私有属性,而留下一组接口——get与set方法来限制用户对于该属性的访问和控制。这样的思想被应用于所有的面向对象高级语言,区别只是在于语法的细节上,例如C++用private声明块来统一指明后面的属性是私有的,而Java则是在属性定义时需要指明private int a;
,Python采用的方法和这两者都不相同,是在属性名前面加两个下划线来指明这是私有属性。
class Student:
def __init__(self,name,score):
self.name=name
self.setScore(score)
def setScore(self,score):
if score>=0 and score<=100:
self.__score=score
else:
print("Wrong data.")
def getScore(self):
return self.__score
def __str__(self):
return "Name:"+self.name+" Score:"+str(self.getScore())
stu1=Student("XiaoMing",95)
print(stu1)
stu1.setScore(-95)
print(stu1.getScore())
这段代码中Student类的实例对象拥有私有属性__score,该属性只能通过get和set方法来访问。虽然用户还是可以修改__score属性,但我们注意到在set方法中,只有合法的成绩数据才能生效,不合法的数据会被过滤掉。另外,留下的接口是什么样的是由我们自己设计的,我们当然可以不留下set接口,从而让外界无法修改内部数据,这就是封装的优势所在。Python在封装部分还有一个语法设计,虽然私有属性无法在类外访问(如果尝试访问,例如print(stu1.__scorre)
会得到AttributeError: 'Student' object has no attribute '__score'
的错误信息),但程序在测试或调试环境中,还是可以通过实例对象名._类名的方式来访问私有属性。例如:
class Student:
def __init__(self,name,score):
self.name=name
self.setScore(score)
def setScore(self,score):
if score>=0 and score<=100:
self.__score=score
else:
print("Wrong data.")
def getScore(self):
return self.__score
def __str__(self):
return "Name:"+self.name+" Score:"+str(self.getScore())
stu1=Student("XiaoMing",95)
print(stu1._Student__score)
在最后一行,stu1._Student__score成功地访问到了stu1的私有属性__score.
2.继承.
首先需要指出的是,Python拥有和C++、Java同样的一个设定:所有的类都继承自object类,即使在定义时没有明确指出,也就是说class A
和class A(object)
是等价的。看到这里,我们也引出了Python中类继承的语法:
class Person:
def __init__(self,name):
self.name=name
def show(self):
print("This is "+self.name+".")
class Student(Person):
pass
stu=Student("XiaoMing")
stu.show()
这段代码除了展示了继承的基本语法以外,还展示了继承技术的功能:子类实例对象拥有父类中定义的属性以及方法。这也是stu.show()能够调用以及输出内容的解释。下面我们进一步完善代码:
class Person:
def __init__(self,name):
self.name=name
print("Person constructor.")
def show(self):
print("This is "+self.name+".")
print("Person show()")
class Student(Person):
def __init__(self,name,score):
self.name=name
self.score=score
print("Student constructor.")
def show(self):
print("This is "+self.name+" and get "+str(self.score)+".")
print("Student show()")
stu=Student("XiaoMing",95)
stu.show()
可以看到在这段代码中,Student有了自己的构造器和show()方法,这两个方法的定义完成了对Person中函数定义的重写。再通过stu进行调用时,都是Student类本身的方法。另外我们也发现,Python中在实例化子类对象时,并不会先实例化基类对象,这和C++以及Java是有着很大区别的。但我们可以在子类对象的__init__()方法中调用基类的构造器:
class Person:
def __init__(self,name):
self.name=name
print("Person constructor.")
def show(self):
print("This is "+self.name+".")
print("Person show()")
class Student(Person):
def __init__(self,name,score):
Person.__init__(self,name)
print("Student constructor.")
self.score=score
def show(self):
print("This is "+self.name+" and get "+str(self.score)+".")
print("Student show()")
stu=Student("XiaoMing",95)
stu.show()
根据运行结果,它确确实实调用了Person类的构造器,我们代码中的Person.__init__(self,name)
还可以是下面的两种语法结构之一:
super(Student,self).__init__(name)
super().__init__(name)
上面的例子中都是单一继承关系,C++中是支持多继承关系的,而Java中的类不支持多继承,只有接口支持多继承。我们看一段代码,介绍一下Python有关多继承的语法:
class Student:
def __init__(self,name,score):
self.name,self.score=name,score
def showStd(self):
print("Name: ",self.name," Score:",self.score)
class Staff:
def __init__(self,Id,salary):
self.Id,self.salary=Id,salary
def showStf(self):
print("Id: ",self.Id," Salary:",self.salary)
class OnJobStudent(Student,Staff):
def __init__(self,name,score,Id,salary):
Student.__init__(self,name,score)
Staff.__init__(self,Id,salary)
me=OnJobStudent("Esper",95,1849,3000)
me.showStf()
me.showStd()
需要注意的是,我们这里调用 Student.__init__(self,name,score)
以及Staff.__init__(self,Id,salary)
时传入的self参数都是同一个,所以如果这两个构造器对同一个属性进行赋值的话,后调用的一个会覆盖掉前一个的结果。
class Student:
def __init__(self,name,score):
self.name,self.score=name,score
def showStd(self):
print("Name: ",self.name," Score:",self.score)
class Staff:
def __init__(self,Id,salary):
self.name,self.salary=Id,salary
def showStf(self):
print("Id: ",self.name," Salary:",self.salary)
class OnJobStudent(Student,Staff):
def __init__(self,name,score,Id,salary):
Student.__init__(self,name,score)
Staff.__init__(self,Id,salary)
me=OnJobStudent("Esper",95,1849,3000)
me.showStf()
me.showStd()
这个例子中,我们在Staff.__init__(self,Id,salary)
中将Id作为参数赋值给name属性,所以得到了下面的结果:
可以发现Student构造器中对name的赋值被覆盖了。
3.多态.
多态是指基类的同一个方法在不同的子类对象中具有不同行为的表现。当通过基类的对象引用调用该多态方法时,程序会根据对象的实际类型来选择合适的方法。
class Person:
def __init__(self,name):
self.name=name
def show(self):
print("Name:",self.name)
class Student:
def __init__(self,name,score):
self.name,self.score=name,score
def show(self):
print("Name: ",self.name," Score:",self.score)
print("Student show().")
class Staff:
def __init__(self,Id,salary):
self.Id,self.salary=Id,salary
def show(self):
print("Id: ",self.Id," Salary:",self.salary)
print("Staff show()")
def printInfo(person):
person.show()
stu=Student("XiaoMing",100)
sta=Staff(9527,3000)
printInfo(stu)
printInfo(sta)
可以看出,同一个printInfo()方法在调用时,会根据接收参数的类型不同,调用合适的方法,Student类型就调用Student的show(),Staff类型就调用Staff的show().
上一篇: 基于百度云的身份证识别
下一篇: 文件小能手---multer