代码运行环境基于Pycharm 24.1,python 3.12
Python面向对象概述
类和对象是面向对象程序设计的两个重要概念。
类和对象的关系即数据类型与变量的关系,根据一个类可以创建多个对象,而每个对象只能是某一个类的对象。
类规定了可以用于存储什么数据,而对象用于实际存储数据,每个对象可存储不同的数据。
类的定义和创建实例
可以直接创建新的类,也可以基于一个或多个已有的类创建新的类;
们既可以创建一个空的类,然后再动态添加属性和方法,也可以在创建类的同时设置属性和方法。
类中的属性对应前面所学习的变量(实例),而类中的方法对应前面所学习的函数。
通过类,可以把数据和操作封装在一起,从而使得程序结构更加清晰,这也就是所谓的类的封装性。
class 类名:
语句1
语句2
...
...
语句N
例:定义一个空类。
class Student: # 定义一个名字为Student的类
pass # 一个空语句,起到占位作用,表示Student类中没有任何属性和方法。
例:创建Student类对象。
Class Student:
pass
if __name__=='__main__':
stu=Student() # 创建Student类的对象,并将创建的对象赋给变量stu
print(stu) # 输出stu:<__main__.Student object at 0x000001523015BAC0>
注意:每次创建对象时,系统都会在内存中选择一块区域分配给对象,每次选择的内存通常时不一样的。因此,实际运行时会看到一个不同的stu对象地址。
类属性定义及其访问
定义类时指定类属性
class Student: # 定义Student类
name='Unknown' # 定义Student类中有一个name属性
对类属性的访问
既可以通过类名访问,也可以通过该类的对象访问。
类名.属性名
对象名.属性名
例:类属性的访问示例。
class Student: # 定义Student类
name='Unknown' # 定义Student类中有一个name属性
if __name__=='__main__':
stu1=Student() # 创建Studentl类对象stu1
stu2=Student() # 创建Studentl类对象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))
第7行输出:stu1 Unknown,stu2 Unknown
第8行输出: 未知
第10行输出:stu1未知,stu2未知
第13行输出: 未知
第14行输出:stu1李晓明,stu2马红
为对象动态绑定新属性
Python作为一种动态语言,除了可以在定义类时指定类属性外,还可以动态地为已创建的对象绑定新的属性。
例:为对象绑定新属示例。
class Student: # 定义Student类
name='Unknown' # 定义Student类中有一个name属性
if __name__=='__main__':
stu1=Student()
stu2=Student()
stu1.age=19 # 为对象stu1动态绑定新的属性age。
print('stu1姓名:%s,年龄:%d'%(stu1.name,stu1.age)) #输出姓名和年龄
stu1姓名:Unknown,年龄:19
类中普通方法定义及调用
与普通函数定义一样,类中的方法在定义时也需要时使用def关键字(也可以有默认参数值)。
类中的方法分为两种,普通方法和内置方法。
普通方法:需要通过类的实例对象根据方法名调用;
实例对象名.方法名(实参列表)
内置方法:是在特定情况下由系统自动执行
注意:类的普通方法必须通过实例对象调用,而不能通过类名直接调用。这是因为通过实例对象调用时会自动将该实例对象传给self,而通过类调用时则不会有这个隐含的参数传递。
普通方法定义和调用
第一个参数需要对应调用方法时所使用的实例对象(一般为self,但也可以改为其他名字)
class Student:
name='Unknown'
def SetName(self,newname,newid='Unknown'): # 定义类的普通方法Setname且有一个默认参数newid
self.name=newname # 将self对应实例对象中的name属性值赋为newname
def PrintName(self): # 定义类的普通方法Printname
print('姓名:%s'%self.name)
if __name__=='__main__':
stu1=Student()
stu2=Student()
stu1.SetName('李晓明') # 实例对象名.方法名(实参列表),self就是实例对象stu1,而newname='李晓明'
stu2.SetName('马红')
stu1.PrintName() # self=stu1
stu2.PrintName()
私有属性
在定义类时,如果一个类属性名是以__(两个下划线)开头,则该类属性为私有属性。
在类内可以直接访问,而在类外无法直接访问的属性。那如果需要在类外访问私有属性怎么办?私有属性名前面加上"_类名"(stu._Student__id)。
class Student:
name='未知' # 定义Student类中有一个name属性
__id='未知' # 定义Student类中有一个__id私有属性
def SetInfo(self,newname,newid): #定义SetInfo方法
self.name=newname
self.__id=newid # 将self对应实例对象的__id属性赋为newiid
def PrintInfo(self):
print('姓名:%s,身份证号:%s'%(self.name,self.__id) ) # 类内访问私有属性__id
if __name__=='__main__':
stu=Student() # 定义Student类对象stu
stu.SetInfo('李晓明','120XXXXXXXXXXXXXX') # 通过实例对象stu调用SetInfo方法
stu.PrintInfo()
print('身份证号 :%s'%stu._Student__id) # 类外访问私有属性,需要在私有属性__id前面加上类名"_Student"
姓名:李晓明,身份证号:120XXXXXXXXXXXXXX
身份证号 :120XXXXXXXXXXXXXX
构造方法
__init__在创建一个类对象时会自动执行,负责完成新创建对象的初始化工作。
例:只有一个参数的构造方法示例。
class Student:
def __init__(self): # 定义构造方法
print('构造方法被调用!')
self.name='未知' # 将self对应的name属性赋为形参name的值
def PrintInfo(self): # 定义一个普通方法
print('姓名:%s'%self.name)
if __name__ == '__main__':
stu1=Student() # 创建Student类对象stu1,自动执行构造方法。
stu1.PrintInfo() # 通过stu1对象调用PrintInfo方法。
构造方法被调用!
姓名:未知
例:带默认参数的构造方法示例。
class Student:
def __init__(self,name='未知'): # 定义带默认参数的构造方法
print('构造方法被调用!')
self.name=name
def PrintInfo(self):
print('姓名:%s'%self.name)
if __name__ == '__main__':
stu1=Student()
stu2=Student('李晓明')
stu1.PrintInfo()
stu2.PrintInfo()
构造方法被调用!
构造方法被调用!
姓名:未知
姓名:李晓明
析构方法
__del__:在销毁一个类对象时会自动执行,负责完成待销毁对象的资源清理工作,如关闭文件等。
注:类对象销毁有如下三种情况:
(1)局部变量的作用域结束。
(2)使用del删除对象。
(3)程序结束时,程序中的所有对象都将被销毁。
注意:如果多个变量对应同一片内存空间,则只有这些变量都删除后才会销毁这片内存空间中所保存的对象,也才会自动执行析构方法。
class Student:
def __init__(self, name): # 定义构造方法
self.name = name
print('姓名为%s的对象被创建!'%self.name)
def __del__(self): # 定义析构方法
print('姓名为%s的对象被销毁!'%self.name)
def func(name):
stu=Student(name)
if __name__=='__main__':
stu1=Student('李晓明')
stu2 = Student('马红')
stu3=stu2
del stu2 # 使用del删除stu2对象,但还有一个stu3所对应的相同内存空间。
func('张刚') # 调用func函数
del stu3 # 使用del删除stu3函数
stu4=Student('刘建')
姓名为李晓明的对象被创建!
姓名为马红的对象被创建!
姓名为张刚的对象被创建!
姓名为张刚的对象被销毁!
姓名为马红的对象被销毁!
姓名为刘建的对象被创建! # 在thonny环境中,输出到此结束。
姓名为李晓明的对象被销毁! # 程序结束所有对象都将被销毁
姓名为刘建的对象被销毁! # 程序结束所有对象都将被销毁
常用内置方法
__str__方法:返回值必须是字符串。对类对象进行处理时或者调用Python内置函数format()和print()时自动执行。
class Complex: # 定义复数类Complex
def __init__(self, real,image):
self.real=real
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) lt(self,other) ge(self,other) le(self,other) eq(self,other) ne(self,other)
class Student:
def __init__(self, name, age):
self.name = name
self.age=age
def __le__(self, other): # 定义内置方法__le__
return self.age <= other.age
if __name__ == '__main__':
stu1=Student('李晓明', 19)
stu2=Student('马红',20)
print('马红的年龄小于等于李晓明的年龄:',stu2<=stu1)
马红的年龄小于等于李晓明的年龄: False
继承的概念
如果一个类C1通过继承已有类C而创建,则将C1称作子类(sub class),将C称作基类、父类或超类(base class、super class)。
子类会继承父类中定义的所有属性和方法,另外也能在子类中增加新的属性和方法。
单继承和多继承
如果一个子类只有一个父类,则将这种继承关系称为单继承;如果一个子类有两个或更多的父类,则将这种继承关系称为多重继承。
注:一个类可能在一种继承关系中是子类,而在另一种继承关系中是父类。
子类的定义
class 子类名(父类名1,父类名2,...,父类名M)
语句1
语句2
...
语句N
class Person:
def SetName(self, name):
self.name = name
class Student(Person): # 以Person类作为父类定义子类Student
def SetSno(self,sno):
self.sno = sno
class Teacher(Person):
def SetTno(self,tno):
self.tno = tno
class TA(Student,Teacher):
def SetTeach(self,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))
学号:1810100,姓名:李晓明
教工号:998012,姓名:马红
方法重写和鸭子类型
方法重写:是指子类可以对从父类中继承过来的方法进行重新定义,从而使得子类对象可以表现出与父类对象不同的行为。
例:方法重写示例。
class Person:
def __init__(self, name):
self.name = name
def PrintInfo(self): # 定义基类中的PrintInfo方法
print('姓名:%s'%self.name)
class Student(Person): #定义一个子类(单继承)
def __init__(self,sno,name):
self.sno = sno
self.name = name
def PrintInfo(self): # 和父类Person中同名的方法
print('学号:%s,姓名:%s'%(self.sno,self.name))
def PrintPersonInfo(person):
print('PrintPersonInfo函数中输出的结果',end='#')
person.PrintInfo() # 通过person调用PrintInfo方法
if __name__ == '__main__':
p=Person('李晓明') # 创建Person对象类p
stu=Student('18100100','李晓明') #创建Student类对象stu
p.PrintInfo() # 调用Person类中的PrintInfo方法
stu.PrintInfo() # 调用Student类中的PrintInfo方法
PrintPersonInfo(p) # p对象所属Person类中的PrintInfo方法
PrintPersonInfo(stu) # stu对象所属Student类中的PrintInfo方法
姓名:李晓明
学号:18100100,姓名:李晓明
PrintPersonInfo函数中输出的结果#姓名:李晓明
PrintPersonInfo函数中输出的结果#学号:18100100,姓名:李晓明
多态
指在执行同样代码的情况下,系统会根据对象实际所属的类去调用相应类中的方法。
鸭子类型
关注的不是对象所属的类,而是一个对象能够如何使用。
在Python中编写一个函数,传递实参前其参数的类型并不确定,在函数中使用形参进行操作时只需要传入的对象能够支持该操作程序就能正常执行。
class Person:
def CaptureImage(self):
print('Person类中的CaptureImage方法被调用!')
class Camera:
def CaptureImage(self):
print('Camera中的CaptureImage方法被调用!')
def CaptureImagetest(arg):
arg.CaptureImage() # 通过arg调用CaptureImage方法
if __name__ == '__main__':
p=Person() # 定义Person类对象p
c=Camera() # 定义Camera类对象c
CaptureImagetest(p) # arg=Person 实际执行的是Person类中的CaptureImage
CaptureImagetest(c) # arg=Camera 实际执行的是Camera类中的CaptureImage
super方法
用于获取父类的代理对象,以执行已在子类中被重写的父类方法,其语法格式为:
super([类名[,对象名或类名]])
例:super(Postgraduate,self).__init__(sno,name)
第一个参数是要获取父类代理对象的类名。
第二个参数如果传入对象名,则该对象所属的类必须是第一个参数指定的类或该类的子类,找到父类对象的self会绑定到这个对象上;如果传入类名,则该类型必须是第一个参数指定的类的子类。
在一个类A的定义中调用super方法时,两个参数都可以省略。此时,super()等价于super(A,self),即获取A的父类代理对象,且获取到的父类代理对象中的self绑定到当前A类对象的self上。
class Person:
def __init__(self, name):
print('Person类构造方法被调用!')
self.name = name
class Student(Person):
def __init__(self,sno,name):
print('Student类构造方法被调用!')
super().__init__(name) #调用父类的构造方法
self.sno = sno
class Postgraduate(Student):
def __init__(self,sno,name,tutor):
print('Postgraduate类构造方法被调用!')
super().__init__(sno,name) #调用父类的构造方法
#上面语句等同于:
#super(Postgraduate,self).__init__(sno,name)
self.tutor = tutor
if __name__=='__main__':
pg=Postgraduate('1810100','李晓明','马红') # 创建Postgraduate类对象pg
print('学号:%s,姓名:%s,导师:%s'%(pg.sno,pg.name,pg.tutor))
Postgraduate类构造方法被调用!
Student类构造方法被调用!
Person类构造方法被调用!
学号:1810100,姓名:李晓明,导师:马红
内置函数
isinstance:
用于判断一个对象所属的类是否是指定类或指定类的子类;
issubclass
用于判断一个类是否是另一个类的子类;
type
用于获取一个对象所属的类。
class Person:
pass
class Student(Person):
pass
class Flower:
pass
if __name==('__main__'):
stu=Student()
f=Flower()
print('stu是Person类或其子类对象:',isinstance(stu,Person))
print('stu是Student类或其子类对象:', isinstance(stu, Student))
""" 输出结果如下:
stu是Person类或其子类对象: True
stu是Person类或其子类对象: True
"""
print('f是Person类或其子类对象:', isinstance(f, Person))
print('student是Person类的子类:', issubclass(Student, Person))
print('Flower是Person类的子类:', issubclass(Flower, Person))
"""输出结果如下:
f是Person类或其子类对象: False
student是Person类的子类: True
Flower是Person类的子类: False
"""
print('stu是Student类:', type(stu)==Student())
'''
stu是Student类: False
'''
类方法和静态方法
类方法是指使用@classmethod修饰的方法,其第一个参数是类本身(而不是类的实例对象)。
类方法的特点是既可以通过类名直接调用,也可以通过类的实例对象调用。
class Complex:
def __init__(self, real=0, image=0):
self.real = real
self.image = image
@classmethod
def add(cls,c1,c2): # 定义类方法add,实现两个复数的加法运算。第一个参数cls指类本身,默认名称为cls。
print(cls) # 输出类方法add的第一个参数,从输出结果中可以看到cls是Complex类。
c=Complex()
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,
''' 也可以使用实例对象调用。
c=c1.add(c1,c2)或c=c2.add(c1,c2)或c=Complex().add(c1,c2)
'''
print('c1+c2的结果为%.2f+%.2fi'%(c.real,c.image))
<class '__main__.Complex'>
c1+c2的结果为3.20+5.60i
动态扩展类与实例和__slots__变量
Python作为一种动态语言,除了可以在定义类时定义属性和方法外,还可以动态地为已创建的对象绑定新的属性和方法。
在给对象绑定方法时,需要使用types模块中的MethodType方法。
注意:给一个对象绑定方法后,只能通过该对象调用该方法,其他未绑定该方法的对象则不调用。下面示例中,没有为stu2对象绑定SetName方法,因此取消注释会报错。而为Student类绑定了SetSno方法,则该类中的所有实例对象都有该方法。
MethodType(SetName,stu1) # 第一个参数是要绑定的函数名,第二个参数是绑定的对象名。
from types import MethodType
class Student:
pass
# 注意:以下两个函数是在类外部定义的。
def SetName(self,name):
self.name = name
def SetSno(self,sno):
self.sno = sno
if __name__=='__main__':
stu1=Student()
stu2=Student()
stu1.SetName=MethodType(SetName,stu1) # 为对象stu1绑定SetName方法
Student.SetSno()=SetSno # 为Student类绑定了SetSno方法,则该类中的所有实例对象都有该方法。
stu1.SetName('李晓明')
stu1.SetSno('1810100')
#stu2.SetName('张刚') # 取消注释会报错 ,因为没有为stu2对象绑定SetName方法。
stu2.SetSno('1810101')
__slots__变量
可以限制可动态扩展的属性。只对__slots__所在类的实例对象有效。
如果子类中没有__slots__定义,则子类的实例对象可以进行任意属性的
动态扩展。
如果子类中有__slots__定义,则子类的实例对象可动态扩展的属性包括
子类中通过__slots__定义的属性和其父类中通过__slots__定义的属性。
class Person:
__slots__ = ('name') # 定义允许动态扩展的属性
class Student(Person): # 以Person类作为父类,定义子类Student类
__slots__ = ('sno') # 定义允许动态扩展的属性
class Postgraduate(Student): # 未定义__solts__属性,因此可以任意扩展属性。
pass
if __name__ == '__main__':
stu=Student()
stu.sno='1810100' # 为stu对象动态扩展属性sno
stu.name='李晓明' # 为stu对象动态扩展属性name
# stu.tutor='马红' # 取消前面的注释运行会报错,因为在Student类与父类Person中通过__solts__方法限制仅可动态扩展name和sno属性。
pg=Postgraduate()
pg.sno='1810101' # 为pg对象动态扩展属性sno
pg.name='张刚' # 为pg对象动态扩展属性name
pg.tutor='马红' # 为pg对象动态扩展属性tutor