面向对象
1、面向对象是当前流行的程序设计方法,其以人类习惯的思维方式,用对象来理解和分析问题空间,使开发软件的方法与过程尽可能接近人类认识世界、解决问题的思维方法与过程。
2、面向对象方法的基本观点是一切系统都是由对象构成的,每个对象都可以接收并处理其他对象发送的消息,它们的相互作用、相互影响,实现了整个系统的运转。
类和对象的概念
1、类和对象是面向对象程序设计的两个重要概念。
2、类和对象的关系即数据类型与变量的关系,根据一个类可以创建多个对象,而每个对象只能是某一个类的对象。
3、类规定了可以用于存储什么数据,而对象用于实际存储数据,每个对象可存储不同的数据。
类的定义和创建实例
类的定义
在一个类中,除了可以包含前面所说的属性,还可以包含各种方法。
属性对应一个类可以用来保存哪些数据,而方法对应一个类可以支持哪些操作(即数据处理)。
类的定义形式多样:
1、我们即可以直接创建新的类,也可以基于一个或多个已有的类创建新的类。
2、我们即可以创建一个空的类,然后在动态添加属性和方法,也可以在创建类的同时设置属性和方法。
注:类中的属性对应前面所学习的变量,而类中的方法对应前面所学习的函数。通过类,可以把数据和操作封装在一起,从而使得程序结构更加清晰,也就所谓的类的封装性。
类的定义形式
class 类名:
语句1
语句2
…
语句n
注:类体的个语句需要采用缩进方式以表示它们是类中的语句。
例:定义一个空类
class Student:#定义一个名字为Student的类
pass #一个空语句,起到占位作用,表示Student类中没有任何属性和方法
创建实例
class Student:#定义一个名字为Student的类
pass#一个空语句,起到占位作用,表示Student类中没有任何属性和方法
if __name__=='__main__':
stu=Student()#创建Student类的对象,并将创建的对象赋给变量stu
print(stu)#输出stu
注:每次创建对象时,系统都会在内存中选择一块区域分配给对象,每次选择的内存通常是不一样的。因此,实际运行时会看到一个不同的stu对象地址。
类属性定义及其访问
定义类时指定类属性
我们可以直接在定义类时定义该类的属性,即类属性。
class Student:#定义Student类
name='Unknown'#定义Student类中其有一个name属性
对类属性的访问,即可以直接通过类名访问,也可以通过该类的对象访问,访问方式为:
类名或对象名.属性名
定义类时指定类属性
例:类属性的访问示例:
class Student:#定义Student类
name='Unknown'#定义Student类中有一个name属性
if __name__='__main__':
print('第4行输出:',Student.name)
stu1=Student()#创建Student类对象stu1
stu2=Student()#创建Student类对象stu2
print('第7行输出:stu1 %s,stu2 %s'%(stu1.name,stu2.name))
Student.name='未知'#将Student的类属性name赋为“未知”
print('第9行输出:',Student,name)
print('第10行输出:stu1 %s,stu2 %s'%(stu1.name,stu2.name))
stu1.name='小明'#将stu1的name属性赋值为“小明”
stu2.name='小红'#将stu2的name属性赋值为“小红”
print('第13行输出:',Student.name)
print('第14行输出:stu1 %s,stu2 %s'%(stu1.name,stu2.name))
Student.name='学生'#将stu1的name属性赋值为“学生”
print('第13行输出:',Student.name)
print('第14行输出:stu1 %s,stu2 %s'%(stu1.name,stu2.name))
为对象动态绑定新属性
Python作为一种动态语言,除了可以在定义类时指定类属性外,还可以动态第为已经创建的对象绑定新的属性。
例:为对象动态绑定新属性示例。
class Student:#定义Student类
name='Unknown'#定义Student类中有一个name属性
if __name__=='__main__':
stu1=Student()#定义Student类对象stu1
stu2=Student()#定义Student类对象stu2
stu1.age=19#为对象stu1动态绑定新的属性age
print('stu1姓名:%s,年龄:%d'%(stu1.name,stu1.age))#输出姓名和年龄
#print('stu2年龄:'%stu2.age)#取消注释则该语句会报错
#peint('使用类名访问年龄属性:'%Student.age)#取消注释则该语句会报错
类中普通方法定义及调用
1、类中的方法实际上就是执行某种数据处理功能的函数。
2、与普通函数定义一样,类中的方法在定义时也需要使用def关键字。
3、类中的方法分为两类:普通方法和内置方法。普通方法需要通过类的实例对象根据方法名调用;内置方法是在特定情况下由系统自动执行。
4、在定义类的普通方法时,要求第一个参数需要对应调用方法时所使用的实例对象(一般命名为self,但也可以改为其他名字)。
当使用一个实例对象调用类的普通方法时,其语法格式为:
实例对象名.方法名(实参列表)
注:在通过类的实例对象调用类中的普通方法时,不需要传入self参数的值,self会自动对应调用该方法时所使用的对象。
例:
class Student:#定义Student类
name='Unknown'#定义Student类中有一个name属性
def SetName(self,newname):#定义类的普通方法SetName
self.name=newname#将self对应实例对象中的name属性值赋为newname
def PrintName(self):#定义类的普通方法PrintName
Print('姓名:%s'%self.name)#输出self对应实例对象中的name属性值
if __name__=='__main__':
stu1=Student()#定义Student类对象stu1
stu2=Student()#定义Student类对象stu2
stu1.SetName('小明')#通过stu1对象调用SetName方法
stu2.SetName('小红')#通过stu2对象调用SetName方法
stu1.PrintName()#通过stu1对象调用PrintName方法
stu2.PrintName()#通过stu2对象调用PrintName方法
注:类的普通方法必须通过实例对象调用,而不能通过类名直接调用。
私有属性
1、私有属性,是指在类内可以直接访问、而在类外无法直接访问的属性。
2、Python中规定,在定义类时,如果一个类属性名是以__(两个下划线)开头,则该类属性为私有属性。
例:
class Student:#定义Student类
name='未知'#定义Student类中有一个name属性
__id='未知'#定义Student类中有一个__id私有属性
def SetInfo(self,newname,newid):#定义SetInfo方法
self.name=newname#将self对应实例对象的name属性赋为newname
self.__id=newid#将self对应实例对象的__id属性赋为newid
def PrintInfo(self):#定义PrintInfo函数
print('姓名:%s,身份证号:%s'%(self.name,self.__id))
if __name__=='__main__':
stu=Student()#定义Student类对象stu
stu.SetInfo('小明','120XXXXXXXXXXXXX')#通过stu调用SetInfo方法
stu.PrintInfo()#通过stu对象调用PrintInfo方法
#print('身份证号:%s'%stu.__id)#取消前面的注释,则程序会报错
注:实际上,Python中并不存在无法访问的私有属性。如果我们在类中定义了一个私有属性,则在类外访问该私有属性时需要在私有属性名前加上“_类名”。
例如,我们只需要将第11行代码改为:
print('身份证号:%s'%stu._Student__id)
注:类中的方法本质上就是前面学习的函数,因此,类中的方法也可以有默认参数值。
例如:
可以将第4行代码修改为:
def SetInfo(self,newname,newid='Unknown'):
将第9行代码修改为:
stu.SetInfo('小明')#通过stu调用SetInfo方法
构造方法
构造方法时Python类中的内置方法之一,它的方法名为__init__,在创建一个类对象时会自动执行,负责完成新创建对象的初始化工作。
例:
只有一个参数的构造方法示例
class Student:#定义Student类
def __init__(self):#定义构造方法
print('构造方法被调用!')
self.name='未知'#将self对应对象的name属性赋值为'未知'
def PrintInfo(self):#定义普通方法PrintInfo
print('姓名:%s'%self.name)#输出姓名信息
if __name__=='__main__':
stu=Student()#创建Student类对象stu,自动执行构造方法
stu.PrintInfo()#通过stu对象调用PrintInfo方法
带默认参数的构造方法示例
class Student:#定义Student类
def __init__(self,name='未知'):#定义构造方法
print('构造方法被调用!')
self,name=name#将self对应对象的name属性赋为形参name的值
def PrintInfo(self):#定义普通方法PrintInfo
print('姓名:%s'%self.name)#输出姓名信息
if __name__=='__main__':
stu1=Student()#创建Student类对象stu1,自动执行构造方法
stu2=Student('小明')
stu1.PrintInfo()#通过stu1对象调用PrintInfo方法
stu2.PrintInfo()#通过stu1对象调用PrintInfo方法
析构方法
析构方法是类的另一个内置方法,它的方法名为__del__,在销毁一个类对象时会自动执行,负责完成待销毁对象的资源清理工作,如关闭文件等。
注:类对象销毁有如下三种情况:
(1)局部变量的作用域结束。
(2)使用del删除对象。
(3)程序结束时,程序中的所有对象都将被销毁。
例:析构方法示例
class Student:#定义Student类
def __init__(self,name):#定义构造方法
self.name=name#将self对应对象的name属性赋值为形参name的值
print('姓名为%s的对象被创建!'%self.name)
def __del__(self):#定义析构方法
print('姓名为%s的对象被销毁!'%self.name)
def func(name):
stu=Student(name)#创建Student类对象stu
if __name__=='__main__':
stu1=Student('小明')#创建Student类对象stu1
stu2=Student('小红')#创建Student类对象stu2
stu3=stu2
del stu2#使用del删除stu2对象
func('张刚')#调用func函数
del stu3#使用del删除stu3对象
stu4=Student('刘建')#创建Student类对象stu4
注:如果多个变量对应同一片内存空间,则只有这些变量都删除后才会销毁这片内存空间中所保存的对象,也才会自动执行析构方法。
常用内置方法
str
调用str函数对类对象进行处理时或者调用Python内置函数format()和print()时自动执行,__str__方法的返回值必须是字符串。
例:
class Complex:#定义复数类Complex
def __init__(self,real,image):#定义构造方法
self.real=real#将self对应对象的real属性赋值为形参real的值
self.image=image#将self对应对象的image属性赋值为形参image的值
def __str__(self):#定义内置方法__str__
return str(self.real)+'+'+str(self.image)+'i'
if __name__=='__main__':
c=Complex(3.2,5.3)#定义Complex类对象c
print(c)#输出“3.2+5.3i”
比较运算的内置方法
类中一组用于比较对象大小的内置方法
内置方法 | 功能描述 |
---|---|
__ gt __(self,other) | 进行self>other运算时自动执行 |
__ lt __(self,other) | 进行self<other运算时自动执行 |
__ ge __(self,other) | 进行self>=other运算时自动执行 |
__ le __(self,other) | 进行self<=other运算时自动执行 |
__ eq __(self,other) | 进行self==other运算时自动执行 |
__ ne __(self,other) | 进行self!=other运算时自动执行 |
类的比较运算内置方法使用示例
class Student:#定义Student类
def __init__(self,name,age):#定义构造方法
self.name=name#将self对应对象的name的值
self.age=age#将self对应对象的age属性赋为形参age的值
def __ le __(self,other):#定义内置方法__le__
return self.age<=other.age
if __name__=='__main__':
stu1=Student('小明',19)#定义Student类对象stu1
stu2=Student('小红',20)3定义Student类对象stu2
print('小红的年龄小于等于小明的年龄:',stu2<=stu1)
继承的概念
1、继承允许开发者基于已有的类创建新的类。
2、如果一个类C1通过继承已有类C而创建,则将C1称作子类(sub class),将C称做基类、父类或超类(base class、super class)。
3、子类会继承父类中定义的所有属性和方法,另外也能够在子类中增加新的属性和方法。
4、如果一个子类只有一个父类,则将这种继承关系称为单继承;如果一个子类有两个或更多父类,则将这种继承关系称为多重继承。
注:需要结合具体的继承关系判断一个类是父类还是子类,一个类可能在一种继承关系中是子类、而是另一种继承关系中是父类。
子类的定义
定义子类时需要指定父类,其语法格式为:
class 子类名(父类名1,父类名2,…,父类名M):
语句1
语句2
…
语句N
当M等于1时,则为单继承;当M大于1时,则为多重继承。
例:
class Person:#定义Person类
def SetName(self,name):#定义SetName方法
self.name=name#将self对应对象的name属性赋为形参name的值
class Student(Person):#以Person类作为父类定义子类Student
def SetSno(self,sno):#定义SetSno方法
self.sno=sno#将self对应对象的sno属性赋为形参sno的值
class Teacher(Person):#以Person类作为父类定义子类Teacher
def SetTno(self,tno):#定义SetTno方法
self.tno=tno#将self对应对象的tno属性赋为形参tno的值
class TA(Studnet,Teacher):#以Student类和Teacher类作为父类定义子类TA
def SetTeacher(self,teacher):#定义SetTeacher方法
self.teacher=teacher#将self对象的teacher属性赋为形参teacher的值
if __name__=='__main__':
stu=Student()#定义Student类对象stu
stu.SetSno('1810100')#调用Student类中定义的SetSno方法
stu.SetName('小明')#调用Student类从Person类继承过来的SetName方法
print('学号:%s,姓名:%s'%(stu.sno,stu.name))#输出学号和姓名
t=Teacher()#定义Teacher类对象t
t.SetTno('998012')#调用Teacher类中定义的SetTno方法
t.SetName('小红')#调用Teacher类从Person类继承过来的SetName方法
print('教工号:%s,姓名:%s'%(t.tno,t.name))#输出教工号和姓名
方法重写和鸭子类型
方法重写
是指子类可以从父类中继承过来的方法进行重新定义,从而使得子类对象可以表现出与父类对象不同的行为。
class Person:#定义Person类
def __init__(self,name):#定义构造方法
self.name=name#将self对象的name属性赋为形参name的值
def PrintInfo(self):#定义PrintInfo方法
print('姓名:%s'%self.name)
class Student(Person):#以Person类作为父类定义子类Student
def __init__(self,sno,name):#定义构造方法
self.sno=sno#将self对象的sno属性赋为形参sno的值
self.name=name#将self对象的name属性赋为形参name的值
def PrintInfo(self):#定义PrintInfo方法
print('学号:%s,姓名:%s'%(self.sno,self.name))
def PrintPersonInfo(person):#定义普通函数PrintPersonInfo
print('PrintPersonInfo函数中的输出结果',end='#')
person.PrintInfo()#通过person调用PrintInfo方法
if __name__=='__main--':
p=Person('小明')#创建Person类对象p
stu=Student('1810100','小明‘)#创建Student类对象stu
p.PrintInfo()
stu.PrintInfo()
PrintPersonInfo(p)
PrintPersonInfo(stu)
注:多态,是指在执行同样代码的情况下,系统会根据对象实际所属的类去调用相应类中的方法。
鸭子类型
1、在鸭子类型中,关注的不是对象所属的类,而是一个对象能够如何使用。
2、在Python中编写一个函数,传递实参前其参数的类型并不确定,在函数中使用形参的类型进行操作时只要传入的对象能够支持该操作程序就能正常执行。
示例:
class Person:#定义Person类
def CaptureImage(self):#定义CaptureImage方法
print('Person类中的CaptureImage方法被调用!')
class Camera:#定义Camera类
def CaptureImage(self):#定义CaptureImage方法
print('Camera类中的CaptureImage方法被调用!')
def CaptureImageTest(arg):#定义CaptureImageTest方法
arg.CaptureImage()#通过arg调用CaptureImage方法
if __name__=='__main__':
p=Person()#定义Person类对象p
c=Camera()#定义Camera类对象c
CaptureImageTest(p)
CaptureImageTest(c)
super方法
super方法用于获取父类的代理对象,以执行已在子类中被重写的父类方法。
super方法有两个参数:
第一个参数时要获取父类代理对象的类名。
第二个参数如果传入对象名,则该对象所属的类必须是第一个参数指定的类或该类的子类,找到的父类对象的self会绑定到这个对象上;如果传入类名,则该类必须是第一个参数指定的类的子类。
在一个类A的定义中调用super方法时,可以将两个参数都省略,此时,super()等价于super(A,self),即获取A的父类代理对象中的self绑定到当前A类对象的self上。
例:super方法使用示例。
class Person:#定义Person类
def __init__(self,name):#定义构造方法
print('Person类构造方法被调用!')
self.name=name#将self对象的name属性赋为形参name的值
class Student(Person):#以Person类作为父类定义子类Student
def __init__(self,sno,name):#定义构造方法
print('Student类构造方法被调用!')
super().__init__(name)#调用父类的构造方法
self.sno=sno#将self对象的sno属性赋为形参sno的值
class Postgraduate(Student):#以Student类作为父类定义子类Postgraduate
def __init__(self,sno,name,tutor):#定义构造方法
print('Postgraduate类构造方法被调用!')
super().__init__(sno,name)#调用父类的构造方法
self.tutor=tutor#将self对象的tutor属性赋为形参tutor的值
if __name__=='__main__':
pg=Postgraduate('1810100','小明','小红')#创建Postgraduate类对象pg
print('学号:%s,姓名:%s,导师:%s'%(pg.sno,pg.name,pg.tutor))
注:将“super().__ init __(sno,name)”改为“super(Postgraduate,self). __ init __(sno,name)”,程序运行结果完全相同。
内置函数isinstance、issubclass和type
isinstance
用于判断一个对象所属的类是否是指定类或指定类的子类
issubclass
用于判断一个类是否是另一个类的子类
type
用于获取一个对象所属的类
例:isinstance、issubclass和type使用示例
class Person:#定义Person类
pass
class Student(Person):#以Person类作为父类定义子类Student
pass
class Flower:#定义Flower类
pass
if __name__=='__main__':
stu=Student()#创建Student类对象stu
f=Flower()#创建Flower对象f
print('stu是Person类或其子类对象:',isinstance(stu,Person))
print('stu是Student类或其子类对象:',isinstance(stu,Student))
print('f是Person类或其子类对象:',isinstance(f,Person))
print('Student是Person类的子类:',issubclass(Student,Person))
print('Flower是Person类的子类:',issubclass(Flower,Person))
print('stu对象所属的类:',type(stu))
print('f对象所属的类:',type(f))
print('stu是Person类对象:',type(stu)==Person)
print('stu是Student类对象:',type(stu)==Student)
注:如果我们要判断一个对象的类型是否是指定类或该类的子类,则可以使用isinstance函数。
如果我们要判断一个对象的类型是否是指定类,则可以使用“type(对象名)==类名”的方式。
类方法和静态方法
类方法
1、类方法是指使用@classmethod修饰的方法,其第一个参数是类本身(而不是类的实例对象)。
2、类方法的特点是既可以通过类名直接调用,也可以通过类的实例对象调用。
class Complex:#定义Complex类
def __init__(self,real=0,image=0):#定义构造方法
self.real=real#初始化一个复数的实部值
self.image=image#初始化一个复数的虚部值
@classmethod
def add(cls,c1,c2):#定义类方法add,实现两个复数的加法运算
print(cls)#输出cls
c=Complex()#创建Complex类对象c
c.real=c1.real+c2.real#实部相加
c.image=c1.image+c2.image#虚部相加
return c
if __name__=='__main__':
c1=Complex(1,2.5)
c2=Complex(2.2,3.1)
c=Complex.add(c1,c2)#直接使用类名调用类方法add
print('c1+c2的结果为%.2f+%.fi'%(c.real,c.image))
注:将第15行的“c=Complex.add(c1,c2)”,改为“c=c1.add(c1,c2)”或“c=c2.add(c1,c2)"或”c=Complex().add(c1,c2)“,程序运行后可得到相同的输出结果,即类方法add的第一个参数,从输出结果中可以看到cls是Complex类。
静态方法
1、静态方法是使用@staticmethod修饰的方法。
2、与类方法相同,静态方法即可以直接通过类名调用,也可以通过类的实例对象调用。
3、与类方法不同的地方在于,静态方法中没有类方法中没有类方法中的第一个类参数。
class Complex:#定义Complex类
def __init__(self,real=0,image=0):#定义构造方法
self.real=real#初始化一个复数的实部值
self.image=image#初始化一个复数的虚部值
@staticmethod
def add(c1,c2):#定义类方法add,实现两个复数的加法运算
c=Complex()#创建Complex类对象c
c.real=c1.real+c2.real#实部相加
c.image=c1.image+c2.image#虚部相加
return c
if __name__=='__main__':
c1=Complex(1,2.5)
c2=Complex(2.2,3.1)
c=Complex.add(c1,c2)#直接使用类名调用类方法add
print('c1+c2的结果为%.2f+%.2fi'%(c.real,c.image))
动态扩展类与实例和__ slots __变量
动态扩展类与实例
1、Python作为一种动态语言,除了可以在定义类时定义属性和方法外,还可以动态地为已经创建的对象绑定新的属性和方法。
2、在对象绑定方法时,需要使用types模块中的MethodType方法,其第一个参数是要绑定的函数名,第二个参数是绑定的对象名。
例:动态扩展类与实例
from type improt MethodType#从types模块中导入MethodType方法
class Student:#定义学生类
pass
def SetName(self,name):#定义SetName函数
self.name=name
def SetSno(self,name):#定义SetName函数
self.sno=sno
if __name__=='__main__':
stu1=Student()#定义Student类对象stu1
stu2=Student()#定义Student类对象stu2
stu1.SetName=MethodType(SetName,stu1)#为stu1对象绑定SetName方法
Student.SetSno=SetSno#为Student类绑定SetSno方法
stu1.SetName('小明')
stu1.SetSno('1810100')
stu2.SetSno('1810101')
注:给一个对象绑定方法后,只要通过该对象调用该方法,其他未绑定该方法的对象则不能调用。例如,没有为stu2对象绑定SetName方法,因此,如果将第15行前面的注释符取消,则程序运行时系统会报错。
而第12行为Student类绑定了SetSno方法,则Student类中的所有实例对象都有该方法。
__ slots __
1、在定义类时,Python提供了__ slots __变量以限制可动态扩展的属性。
2、 __ slots __ 中所做的动态扩展属性限制只对 __ slots 所在类的实例对象有效。
3、如果子类中没有 slots 定义,则子类的实例对象可以进行任意属性的动态扩展。
4、如果子类中有 slots 定义,则子类的实例对象可动态扩展的属性包括子类中通过 slots __定义的属性和其父类中通过 __ slots __定义的属性。
例:__ slots __使用示例
class Person:#定义Person类
__ slots __=('name')#定义允许动态扩展的属性
class Student(Person):#以Person类作为父类定义子类Student类
__ slots __=('sno')#定义允许动态扩展的属性
class Postgraduate(Student):#以Student类作为父类定义子类Postgraduate类
pass
if __name__=='__main__':
stu=Student()#定义Student类对象stu
sut.sno='1810100'#为stu对象动态扩展属性sno
stu.name='小明'#为stu对象动态扩展属性name
pg=Postgraduate()#定义Postgraduate类对象pg
pg.sno='1810101'#为pg对象动态扩展属性sno
pg.name='张刚'#为pg对象动态扩展属性name
pg.tutor='小红'#为pg对象动态扩展属性tutor
@property装饰器
1、类中的属性可以直接访问和赋值,这为类的使用者提供了方便,但也带来了问题:类的使用者可能会给一个属性赋上超出有效范围的值。
2、为了解决这个问题,Python提供了@property装饰器,可以将类中属性的访问和赋值操作自动转为方法调用,这样可以在方法中对属性值的取值范围做一些条件限定。
3、直接使用@property就可以定义一个用于获取属性值的方法(即getter)。
4、如果要定义一个设置属性值的方法(setter),则需要使用名字“@属性名.setter”的装饰器。
5、如果一个属性只有用于获取属性值的getter方法,而没有用于设置值的setter方法,则该属性是一个只读属性,只允许读取该属性的值、而不能设置该属性的值。
例:通过@property装饰器使得学生成绩的取值范围必须在0~100之间。
improt datetime
class Student:#定义Student类
@property
def score(self):#用@property装饰器定义一个用于获取score值的方法
return self._score
注:在类的setter和getter方法中使用self访问属性时,需要在属性名前加上下划线,否则系统会因不断递归调用而报错。
@score.setter
def score(self,score):#用score.setter定义一个用于设置score值的方法
if score<0 or score>100:#不符合0~100的限定条件
print('成绩必须在0~100之间!')
else:
self._score=score
@property
def age(self):#用@property装饰器定义一个用于获取age值的方法
return datetime.datetime.now().year-self.birthyear
if __name__=='__main__':
stu=Student()#创建Student类对象stu
stu.score=80#将stu对象的score属性赋值为80
stu.birthyear=2000#将stu对象的birthyear属性赋值为2000
print('年龄:%d,成绩:%d'%(stu.age,stu.score))