面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。
类和对象是面向对象程序设计的两个重要概念。
类和对象的关系即数据类型与变量的关系,根据一个类可以创建多个对象,而每个对象只能是某一个类的对象。类规定了可以用于存储什么数据,而对象用于实际存储数据,每个对象可存储不同的数据。
与C/C++等语言不同,Python中提供的基本数据类型也是类,如int、float等。
类中可以包含各种属性及各种方法。属性对应一个类可以用来保存哪些数据,而方法对应一个类可以支持哪些操作(即数据处理)。
通过类,可以把数据和操作封装在一起,从而使得程序结构更加清晰,这也就是所谓的类的封装性。例:定义一个空类
class Student: #定义一个名字为Student的类
pass #一个空语句,起到占位作用,表示Student类中
#没有任何属性和方法
if __name__=='__main__':
stu=Student() #创建Student类的对象,并将创建的对象赋给变量stu
print(stu) #输出stu
# 输出
<__main__.Student object at 0x00000216EE7DF0F0>
# 提示:每次创建对象时,系统都会在内存中选择一块区域分配给对象,每次
# 选择的内存通常是不一样的。因此,实际运行时会看到一个不同的stu对象地址。
对类属性的访问,既可以直接通过类名访问,也可以通过该类的对象
访问,访问方式为:
类名或对象名.属性名
私有属性,是指在类内可以直接访问、而在类外无法直接访问的属性。
Python中规定,在定义类时,如果一个类属性名是以__(两个下划线)开头,则该类属性为私有属性。
实际上,Python中并不存在无法访问的私有属性。如果我们在类中定
义了一个私有属性,则在类外访问该私有属性时需要在私有属性名
前加上“_类名”。例如:
class Student: #定义Student类
__id='未知' #定义Student类中有一个__id私有属性
if __name__=='__main__':
stu=Student() #定义Student类对象stu
print('身份证号:%s'%stu._Student__id)
类中的方法分为两类:普通方法和内置方法。
• 普通方法需要通过类的实例对象根据方法名调用;
• 内置方法是在特定情况下由系统自动执行。
在定义类的普通方法时,要求第一个参数需要对应调用方法时所使用的实例对象(一般命名为self,但也可以改为其他名字)。在通过类的实例对象调用类中的普通方法时,并不需要传入self参数的值,self会自动对应调用该方法时所使用的对象。
注意:通过实例对象调用时会自动将该实例对象传给self,而通过类调用时则不会有这个隐含的参数传递。
构造方法是Python类中的内置方法之一,它的方法名为__init__,在创建一个类对象时会自动执行,负责完成新创建对象的初始化工作。
析构方法是类的另一个内置方法,它的方法名为__del__,在销毁一个类对象时会自动执行,负责完成待销毁对象的资源清理工作,类对象销毁有如下三种情况:
(1)局部变量的作用域结束。
(2)使用del删除对象。
(3)程序结束时,程序中的所有对象都将被销毁。
常见的内置方法
__str__ 函数 :str函数对类对象进行处理时或者调用Python内置函数format()和print()时自动执行,__str__方法的返回值必须是字符串。
比较运算的内置方法
__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属性赋为形参
#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) #定义Student类对象stu2
print('马红的年龄小于等于李晓明的年龄:', stu2<=stu1)
#输出
马红的年龄小于等于李晓明的年龄:False
内置函数isinstance 用于判断一个对象所属的类是否是指定类或指定类的子类;
内置函数issubclass 用于判断一个类是否是另一个类的子类;
内置函数type 用于获取一个对象所属的类。
如果我们要判断一个对象的类型是否是指定类或该类的子类,则可以使用isinstance函数。
如果我们要判断一个对象的类型是否是指定类,则可以使用“type(对象名)==类名”的方式。
继承
继承允许开发者基于已有的类创建新的类。
-如果一个类C1通过继承已有类C而创建,则将C1称作子类(subclass),将C称做基类、父类或超类(base class、super class)。
-子类会继承父类中定义的所有属性和方法,另外也能够在子类中增加新的属性和方法。
-如果一个子类只有一个父类,则将这种继承关系称为单继承;如果一个子类有两个或更多父类,则将这种继承关系称为多重继承。
子类的定义 。定义子类时需要指定父类,格式为:
class 子类名(父类名1, 父类名2, …, 父类名M):
语句1
语句2
…
语句N
方法重写 是指子类可以对从父类中继承过来的方法进行重新定义,从而使得
子类对象可以表现出与父类对象不同的行为。
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)
#输出
李晓明
1810100,姓名:李晓明
李晓明
1810100,姓名:李晓明
多态,是指在执行同样代码的情况下,系统会根据对象实际所属的类去调用相应类中的方法。
super方法
super方法用于获取父类的代理对象,以执行已在子类中被重写的父
类方法,其语法格式为:
super([类名[, 对象名或类名]])
super方法有两个参数:
第一个参数是要获取父类代理对象的类名。
第二个参数如果传入对象名,则该对象所属的类必须是第一个参数指定的类或该类的子类,找到的父类对象的self会绑定到这个对象上;如果传入类名,则该类必须是第一个参数指定的类的子类。
在一个类A的定义中调用super方法时,可以将两个参数都省略,此时,super()等价于super(A, self),即获取A的父类代理对象,且获取到的父类代理对象中的self绑定到当前A类对象的self上。
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) #调用父类的构造方法9
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))
#输出
Postgraduate类构造方法被调用!
Student类构造方法被调用!
Person类构造方法被调用!
学号:1810100,姓名:李晓明,导师:马红
“super().__init__(sno,name)”与“super(Postgraduate,self).__init__(sno, name) ”等价。
类方法
类方法是指使用@classmethod修饰的方法,其第一个参数是类本身(而不是类的实例对象)。
类方法的特点是既可以通过类名直接调用,也可以通过类的实例对象调用。
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+%.2fi'%(c.real,c.image))
#输出
<class '__main__.Complex'>
c1+c2的结果为3.20+5.60i
“c=Complex.add(c1,c2) ”改为“c=c1.add(c1, c2) ”或
“c=c2.add(c1, c2)”或“c=Complex().add(c1, c2) ”,程序运行后可得到相同的输出结果,即类方法也可以使用实例对象调用。
通过“print(cls) ”输出类方法add的第一个参数,从输出结果中可以看到cls是Complex类。
静态方法
静态方法是指使用@staticmethod修饰的方法。静态方法既可以直接通过类名调用,也可以通过类的实例对象调用。
与类方法不同的地方在于,静态方法中没有类方法中的第一个类参数。
@staticmethod
def add(c1,c2): #定义类方法add,实现两个复数的加法运算
c=Complex() #创建Complex类对象c
c.real=c1.real+c2.real #实部相加
c.image=c1.image+c2.image #虚部相加
return c
动态扩展类
Python作为一种动态语言,除了可以在定义类时定义属性和方法外,还可以动态地为已经创建的对象绑定新的属性和方法。
在给对象绑定方法时,需要使用types模块中的MethodType方法,其第一个参数是要绑定的函数名,第二个参数是绑定的对象名。
from types import MethodType #从types模块中导入MethodType方法
class Student: #定义学生类
pass
def SetName(self,name): #定义SetName函数
self.name=name
def SetSno(self,sno): #定义SetSno函数
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.SetName('张刚') #取消注释则会报错,因为职位stu1绑定了方法
stu2.SetSno('1810101')
__slots__函数:在定义类时,Python提供了__slots__变量以限制可动态扩展的属性。该限制只对__slots__所在类的实例对象有效。如果子类中没有__slots__定义,则子类的实例对象可以进行任意属性的动态扩展。
(一般也用不到,作为了解)
@property装饰器
类中的属性可以直接访问和赋值,这为类的使用者提供了方便,但也带来了问题:类的使用者可能会给一个属性赋上超出有效范围的值。
为了解决这个问题,Python提供了@property装饰器,可以将类中属性的访问和赋值操作自动转为方法调用,这样可以在方法中对属性值的取值范围做一些条件限定。
直接使用@property就可以定义一个用于获取属性值的方法(即getter)。如果要定义一个设置属性值的方法(setter),则需要使用名字“@属性名.setter”的装饰器。
如果一个属性只有用于获取属性值的getter方法,而没有用于设置属性值的setter方法,则该属性是一个只读属性,只允许读取该属性的值、而不能设置该属性的值。
import datetime
class Student: #定义Student类
@property
def score(self): #用@property装饰器定义一个用于获取score值的方法
return self._score
@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))
#输出
年龄:18,成绩:80
#stu.age=19 #取消前面的注释符则会报错
stu.score=105 #将stu对象的score属性赋值为105
print('年龄:%d,成绩:%d'%(stu.age,stu.score))
注意:在类的setter和getter方法中使用self访问属性时,需要在属性名前加上下划线,否则系统会因不断递归调用而报错。
end