关于什么是面向对象及相关的一些概念不在本篇的说明范围,以下是关于python中类的使用方式的说明。
目录
■ 方法搜索顺序 MRO(Method Resolution Order)
一,简单类的定义及使用
1. 定义只包含方法的类
语法格式:
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表:
pass
注意:
类名的命名规则要符合大驼峰命名法:每个单词的首字母大写;
类中的方法定义时,第一个参数必须是self;
2. 创建类的对象
语法格式:
对象变量 = 类名()
3. 定义一个类并创建对象示例
定义一个类Rectangle;
定义两个方法,length()表示计算周长,area()表示计算面积;
不定义属性;
class Rectangle:
def length(self):
print("计算周长")
def area(self):
print("计算面积")
if __name__ == '__main__':
rect = Rectangle()
rect.length()
rect.area()
显示结果:
计算周长
计算面积
二,方法中的self参数
方法中的self参数类似C++类中的this。类封装的方法内部,self表示当前调用方法的对象自己。
- 对象调用方法时,不需要传递self参数;
- 在方法内部,可以使用self.访问对象的属性或其它的方法;
如上示例,调整需要在一个函数cal()中计算面积和周长,那么可以把代码调整为:
class Rectangle:
def length(self):
print("计算周长")
def area(self):
print("计算面积")
def cal(self):
self.area()
self.length()
if __name__ == '__main__':
rect = Rectangle()
rect.cal()
显示结果:
计算面积
计算周长
三,python提供的一些与类创建/销毁相关的内置方法
python提供了以下内置函数,在创建类对象或销毁时会被自动调用。
方法名 | 类型 | 作用 |
__new__ | 方法 | 用来创建对象,会被自动调用 |
__init__ | 方法 | 对象初始化操作,会被自动调用 |
__del__ | 方法 | 对象从内存中销毁前操作,会被自动调用 |
__str__ | 方法 | 返回对象描述信息(字符串类型),可以使用print函数输出 |
- 使用类名()创建对象时,python首先调用 __new__方法为对象分配空间,__new__会返回对象的引用;
- python获得对象的引用后,将引用作为第一个参数,传递给__init__方法,为对象的属性设置初始值;
- print(对象名)如果想打印自定义的内容,调用__str__()方法;
- 当一个对象被从内存中销毁前,会自动调用 __del__() 方法。
__init__和__del__的应用场景:
- __init__可以改造初始化方法,可以让创建对象更加灵活。
- __del__ 如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__方法;
1. 新建对象 __new__()
使用 类名() 的形式创建对象时,python首先会调用new方法为对象分配空间,new方法是object基类提供的内置静态方法,主要的作用有:
- 在内存中为对象分配空间;
- 返回对象的引用;
python获得new返回的对象的引用后,将引用作为第一个参数,传递给__init__方法。
重写__new__方法的代码是固定的:
- 重写的__new__()方法最后一定返回 return supre().__new__(cls)
需要注意:__new__是一个静态方法,在调用时需要主动传递cls参数。
示例:单例模式设计时,用到该方法。
class MusicPlayer(object):
instance = None
def __new__(cls,*args,**kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
print("初始化音乐播放器")
if __name__ == '__main__':
player = MusicPlayer()
player1 = MusicPlayer()
print(player)
print(player1)
显示结果:
初始化音乐播放器
初始化音乐播放器
<__main__.MusicPlayer object at 0x000001D5A65C4220>
<__main__.MusicPlayer object at 0x000001D5A65C4220>
2. 初始化 __init__()
当创建对象时,会自动执行一下操作:
- 创建对象(new)——为对象在内存中分配空间;
- 初始化方法(init)——为对象的属性设置初始值;
初始化方法就是__init__方法,__init__是对象的内置方法。
该函数不需要外部调用,创建对象时会自动创建。
个人理解 __new__+__init__()函数类似C++中类的构造函数。
专门用来定义一个类的属性。可以在__init__方法内部使用 self.属性名 = 属性的初始值 来定义属性。定义属性之后,创建的对象都拥有该属性。
如下示例:
class Rectangle:
def __init__(self):
print("初始化")
self.width = 4
if __name__ == '__main__':
rect = Rectangle()
rect1 = Rectangle()
print(rect.width)
print(rect1.width)
显示结果:
初始化
初始化
4
4
■ 初始化的同时设置初始值
如果希望创建对象的同时,就设置对象的属性,可以改造__init__方法
- 把希望设置的属性值,定义成 __init__ 的参数
- 在方法内部使用self.属性 = 形参 接收外部传递的参数
- 在创建对象时,使用类名(属性1,属性2,...)调用
个人理解 这种方式类似于 创建一个有参数列表的构造函数。
使用示例:
class Rectangle:
def __init__(self,width,height):
print("初始化")
self.width = width
self.height = height
def length(self):
print("计算周长")
return (self.width + self.height)*2
def area(self):
print("计算面积")
return self.width*self.height
if __name__ == '__main__':
rect = Rectangle(3,5)
rect1 = Rectangle(6,7)
print(rect.length())
print(rect.area())
print(rect1.length())
print(rect1.area())
显示结果:
初始化
初始化
计算周长
16
计算面积
15
计算周长
26
计算面积
42
3. 对象销毁前调用 __del__()
当一个对象被从内存中销毁前,会自动调用__del__方法。
对象的生命周期:
- 一个对象从创建,生命周期开始;
- 一个对象的 __del__ 方法一旦被调用,生命周期结束;
- 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
使用示例:
class Rectangle:
def __init__(self,width,height):
print("初始化")
self.width = width
self.height = height
def length(self):
print("计算周长")
return (self.width + self.height)*2
def area(self):
print("计算面积")
return self.width*self.height
def __del__(self):
print("结束")
if __name__ == '__main__':
rect = Rectangle(3,5)
print(rect.length())
del rect # del关键字删除变量
print(rect.area())
print("="*50)
运行时会提示 NameError: name 'rect' is not defined,这是因为在打印面积前已经把rect对象删除了。
4. 自定义对象的打印内容 __str()__
python中,如果使用print(对象),默认情况下,会输出这个对象是由哪个类创建的,以及在内存中的地址,如下:
class Rectangle:
def __init__(self):
print("初始化")
def __del__(self):
print("结束")
if __name__ == '__main__':
rect = Rectangle()
print(rect) # 输出 <__main__.Rectangle object at 0x0000025E54966BB0>
如果在开发中,希望使用 print(对象)时,能够打印自定义的内容,可以使用 __str__ 方法。但是需要注意的是 __str__ 方法必须返回一个字符串。
示例:
class Rectangle:
def __init__(self):
print("初始化")
def __str__(self):
return "这是一个矩形类"
def __del__(self):
print("结束")
if __name__ == '__main__':
rect = Rectangle()
print(rect) # 输出 这是一个矩形类
四, 类的私有属性和方法
在定义属性或方法时,在属性名或者方法名前加两个下划线,就表示该方法或属性是私有属性或方法。如定义 self.__name,__name就是私有属性;如self.__get_length(),__get_length()就是私有方法。
class Rectangle:
def __init__(self):
print("初始化")
self.__name = '矩形'
def __str__(self):
return "这是一个矩形类"
def __length(self):
print('计算矩形周长')
def __del__(self):
print("结束")
if __name__ == '__main__':
rect = Rectangle()
print(rect.__name)
print(rect.__length())
运行到 print(rect.__name) 时,会报错:AttributeError: 'Rectangle' object has no attribute '__name',因为__name是私有属性,外部是不能访问的,所以会提示没有__name属性。
■ 要在外部访问私有属性和私有方法,可以使用 对象._类名__属性/方法名的方式访问,但不推荐使用该种方法。
class Rectangle:
def __init__(self):
print("初始化")
self.__name = '矩形'
def __str__(self):
return "这是一个矩形类"
def __length(self):
print('计算矩形周长')
def __del__(self):
print("结束")
if __name__ == '__main__':
rect = Rectangle()
rect._Rectangle__length()
显示结果:
初始化
计算矩形周长
结束
五,继承
面向对象的三大特性:封装、继承、多态。
- 封装:上面讲到的类就实现了封装的特性,根据 职责 把 属性和方法 封装到一个抽象的类中。
- 继承:本节是关于继承的内容,继承可以实现代码的重用,相同的代码不需要重复编写。
- 多态:下一节是关于多态的描述,多态增加代码的灵活度,不同的对象调用相同的方法,产生不同的执行结果。
1. 继承简单介绍
简单来说,继承是:子类拥有父类的所有方法和属性。如下列的类,不使用继承时,每个类中都有相同的方法名;使用继承后,子类可以使用父类的方法。
- 子类继承自父类,可以直接使用父类中已经封装的方法;
- 子类中可以根据职责,封装子类特有的属性和方法;
- 继承可以传递,子类拥有父类及父类的父类中封装的所有属性和方法;
使用继承的情况下,CAnimal是CDog和CCat的父类,CDog和CCat是CAnimal的子类,CDog和CCat从CAnimal类继承;父类又称为基类,子类又称为派生类。
继承使用示例:
# 基类
class CAnimal:
def __init__(self):
print("父类:Animal 初始化")
def eat(self):
print("eating...")
def drink(self):
print("drinking...")
# 派生类
class CMouse(CAnimal):
def __init__(self):
print("子类:CDog 初始化")
def steal(self):
print("OMG,the mouse is steal rice.")
# 派生类
class CCat(CAnimal):
def __init__(self):
print("子类:CCat 初始化")
def catch(self):
print("The cat is watching the mouse and prepare catch it.")
if __name__ == "__main__":
ani = CAnimal()
jerry_mouse = CMouse()
tom_cat = CCat()
jerry_mouse.eat() # 使用的是基类中的eat()方法
jerry_mouse.drink() # 使用的是基类中的drink()方法
tom_cat.catch()
显示结果:
父类:Animal 初始化
子类:CDog 初始化
子类:CCat 初始化
eating...
drinking...
The cat is watching the mouse and prepare catch it.
■ 需要注意的是:
子类不能访问父类的私有属性和方法,父类的私有属性和方法仅可以父类的对象或方法访问。
2. 单继承和多继承
根据基类的个数可以分为单继承和多继承。只继承自一个基类称为单继承,继承自多个基类称为多继承。
单继承和多继承的语法格式:
# 单继承
class 类名(父类名):
pass
# 多继承
class 类名(父类名1,父类名2,...):
pass
多继承时需要注意:应该避免不同的父类中存在同名的方法或属性。
■ 方法搜索顺序 MRO(Method Resolution Order)
python针对类提供了内置的属性 __mro__,可以查看方法的搜索顺序,主要用于在多继承时判断方法、属性的调用路径。
- 如果在当前类中找到方法,就直接执行,不再搜索;
- 如果没有找到,查找下一个类中是否又对应的方法,如果找到,直接执行,不再搜索;
- 如果找到最后一个类还没有找到对应的方法,那么程序报错;
示例:
class CAnimal:
def __init__(self):
print("父类:Animal 初始化")
class CMouse(CAnimal):
def __init__(self):
print("子类:CDog 初始化")
class CCat(CAnimal):
def __init__(self):
print("子类:CCat 初始化")
if __name__ == "__main__":
ani = CAnimal()
jerry_mouse = CMouse()
tom_cat = CCat()
tom_cat.catch()
显示结果:首先查找当前类CCat,然后查找基类CAnimal,最后查找object类。
(<class '__main__.CCat'>, <class '__main__.CAnimal'>, <class 'object'>)
3. 新式类与经典类
上面的查找结果中最后查找的是object类。
- 新式类:以object为基类的类,推荐使用;
- 经典类:不以object为基类的类,不推荐使用;
object类是python为所有对象提供的基类,提供有一些内置的属性和方法(可以使用dir()查看提供的内置属性和方法)。
- 在python3.x中定义类时,如果没有指定父类,会默认使用object作为该类的基类;
- 在python2.x中定义类时,如果没有指定父类,不会以obeject作为基类;
所以建议在创建类时,如果没有父类,使用统一继承自object.
class 类名(object):
pass
4. 方法重写 override
通常情况下,子类拥有父类的所有方法和属性,可以直接使用父类中已经封装好的方法,不需要再次开发。但是当父类的方法实现不能满足子类需求时,可以对方法进行重写。
重写有两种情况:
- 覆盖 父类的方法
- 对父类方法进行扩展
■ 覆盖父类方法
当父类的方法实现和子类的方法实现完全不同,可以使用覆盖的方式,在子类中重新编写父类的方法的实现。覆盖的具体实现方式就是在子类中重新实现一个和父类同名的方法,在重写之后,运行时,子类只会调用子类中重写的方法,不会再调用父类封装的方法。
覆盖示例:
class CAnimal:
def __init__(self):
print("父类:Animal 初始化")
def eat(self):
print("eating...")
class CMouse(CAnimal):
def __init__(self):
print("子类:CDog 初始化")
def eat(self):
print("咯吱咯吱的吃")
class CCat(CAnimal):
def __init__(self):
print("子类:CCat 初始化")
def eat(self):
print("优雅的吃")
if __name__ == "__main__":
ani = CAnimal()
jerry_mouse = CMouse()
tom_cat = CCat()
ani.eat()
jerry_mouse.eat()
tom_cat.eat()
显示结果:
父类:Animal 初始化
子类:CDog 初始化
子类:CCat 初始化
eating...
咯吱咯吱的吃
优雅的吃
■ 扩展父类方法
如果子类的方法实现中包含父类的方法实现,或者说父类原本封装的方法实现是子类方法的一部分,此时,就可以使用扩展的方式,
- 在子类中重写父类的方法,
- 在需要父类实现的位置使用super().父类方法来调用父类方法的执行,
- 其它位置编写子类特有的代码实现
super是python的一个特殊类;
super()标识使用super类创建一个对象;
常用的使用场景是在重写父类方法时,调用父类中封装的方法实现;
- 在python2.x中如果需要调用父类的方法,还可以使用 父类名.方法(self) 的方式,但不推荐使用,因为一旦父类发生变化,方法调用位置的类名就需要修改。
使用示例:
class CAnimal:
def __init__(self):
print("父类:Animal 初始化")
def eat(self):
print("eating...")
class CMouse(CAnimal):
def __init__(self):
print("子类:CDog 初始化")
def eat(self):
super().eat()
print("咯吱咯吱的吃")
class CCat(CAnimal):
def __init__(self):
print("子类:CCat 初始化")
def eat(self):
super().eat()
print("优雅的吃")
if __name__ == "__main__":
ani = CAnimal()
jerry_mouse = CMouse()
tom_cat = CCat()
ani.eat()
jerry_mouse.eat()
tom_cat.eat()
显示结果:
父类:Animal 初始化
子类:CDog 初始化
子类:CCat 初始化
eating...
eating...
咯吱咯吱的吃
eating...
优雅的吃
六,多态
多态增加了代码的灵活性,不同的子类对象调用相同的父类方法,产生不同的执行结果。多态以继承和重写父类方法为前提,不会影响到类的内部设计。
如上,CDog是CHuskie和CXiaoTianQuan的基类,在子类中重写了父类的play()方法,那么不同的子类对象调用play()方法时,产生的结果也不同。
示例:
class CDog(object):
def __init__(self,name):
self.name = name
def play(self):
print(f"{self.name} 自己在玩耍")
class CXiaoTianQuan(CDog):
def play(self):
print("%s 在追沉香" % self.name)
class CHuskie(CDog):
def play(self):
print(" %s 在拆家" % self.name)
class CPerson(object):
def __init__(self,name):
self.name = name
def play_with(self,dog:CDog):
print("%s想和%s一起玩耍,但是,"% (self.name,dog.name))
dog.play()
if __name__ == "__main__":
xiaohei = CXiaoTianQuan("哮天犬")
erha = CHuskie("哈士奇")
lihua = CPerson('李华')
lihua.play_with(xiaohei)
lihua.play_with(erha)
显示结果,在CPerson类中的play_with()方法中不关注是什么类型的狗对象,直接调用play()方法;程序执行时,传入不同的实参,执行不同的结果。
李华想和哮天犬一起玩耍,但是,
哮天犬 在追沉香
李华想和哈士奇一起玩耍,但是,
哈士奇 在拆家
七,类属性和类方法
在python中,一切皆对象!!
- class AA:AA定义的类属于类对象;
- obj1=AA():obj属于实例对象;
在python中,类是一个特殊的对象——类对象。程序运行时,类会被加载到内存中,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例。
类对象可以拥有自己的属性和方法:类属性、类方法。通过 类名. 的方式访问类属性或者调用类方法。该类的所有对象共享类属性和类方法。
1. 类属性
类属性是给类对象定义的属性,通常用来记录与这个类相关的特性,不会记录具体对象的特征。使用赋值语句在class关键字下方可以定义类属性,如:
class 类名(object):
count=0 # count就是定义的类属性
如下示例,定义一个工具类,其定义如下:
类型 | 变量 | 说明 |
类属性 | count | 记录该类创建的对象的个数 |
实例属性 | name | 每一个对象都有一个name属性,展示实例对象本身 |
class Tools(object):
# 定义类属性count,记录创建的Tools实例对象的个数
count = 0
def __init__(self,name):
# 定义对象属性
self.name = name
# 访问类属性,每创建一个对象count就加1
Tools.count+=1
if __name__ == "__main__":
tool1 = Tools('钳子')
tool2 = Tools('扳子')
tool3 = Tools('剪子')
print(f'tool1 name = {tool1.name}') # 输出 tool1 name = 钳子
print(f'tool2 name = {tool2.name}') # 输出 tool2 name = 扳子
print(f'tool3 name = {tool3.name}') # 输出 tool3 name = 剪子
print(Tools.count) # 输出 3
类属性获取是向上查找机制,首先在对象内部查找对象属性,如果没有找到就会向上寻找类属性。
2. 类方法
类方法是针对类对象定义的方法,在类方法内部可以直接访问类属性或者调用其它的类方法
类方法语法格式:
@classmethod
def 类方法名(cls):
pass
说明:
- 类方法需要用修饰器 @classmethod 标识,表示这是一个类方法;
- 类方法的第一个参数应该是cls:
- 由哪一个类调用的方法,方法内的cls就是哪一个类的引用;
- cls参数和实例方法的self参数类似;
- 也可以使用其它名字,建议使用cls;
- 通过 类名. 调用类方法,调用方法时,不需要传递cls参数;
- 类方法内部,可以通过 cls. 访问类属性或调用其它的类方法;
示例,对如上Tools改造,增加类方法 show_tool_count()打印该类创建的对象的个数。
class Tools(object):
# 定义类属性count,记录创建的Tools实例对象的个数
count = 0
# 定义类方法
@classmethod
def show_tool_count(cls):
print(f'Tools类创建的对象有{cls.count}个')
def __init__(self,name):
# 定义实例属性
self.name = name
# 访问类属性,每创建一个对象count就加1
Tools.count+=1
if __name__ == "__main__":
tool1 = Tools('钳子')
tool2 = Tools('扳子')
tool3 = Tools('剪子')
print(f'tool1 name = {tool1.name}') # 输出 tool1 name = 钳子
print(f'tool2 name = {tool2.name}') # 输出 tool2 name = 扳子
print(f'tool3 name = {tool3.name}') # 输出 tool3 name = 剪子
print(Tools.count) # 输出 3
Tools.show_tool_count() # 输出 Tools类创建的对象有3个
3. 类中的静态方法
开发时,如果要封装一个类的方法,该方法不需要访问 实例属性、实例方法、类属性、类方法,那么就可以把这个方法封装成一个静态方法。
静态方法语法格式:
@staticmethod
def 静态方法名():
pass
说明:
- 静态方法需要用修饰器 @staticmethod 标识,表示这是一个静态方法;
- 通过 类名. 调用静态方法;
示例:
class Tools(object):
# 定义类属性count,记录创建的Tools实例对象的个数
count = 0
@classmethod
def show_tool_count(cls):
print(f'Tools类创建的对象有{cls.count}个')
@staticmethod
def run():
print('开始工作了')
def __init__(self,name):
# 定义实例属性
self.name = name
# 访问类属性,每创建一个对象count就加1
Tools.count+=1
if __name__ == "__main__":
Tools.run()
tool1 = Tools('钳子')
tool2 = Tools('扳子')
tool3 = Tools('剪子')
print(f'tool1 name = {tool1.name}') # 输出 tool1 name = 钳子
print(f'tool2 name = {tool2.name}') # 输出 tool2 name = 扳子
print(f'tool3 name = {tool3.name}') # 输出 tool3 name = 剪子
print(Tools.count) # 输出 3
Tools.show_tool_count() # 输出 Tools类创建的对象有3个
显示结果:
开始工作了
tool1 name = 钳子
tool2 name = 扳子
tool3 name = 剪子
3
Tools类创建的对象有3个
使用示例:
定义一个学生类Student,记录分数。类的属性和方法如下:
代码:
class Student(object):
# 定义类属性
top_score = 0
top_name = ''
# 定义类方法
@classmethod
def show_top_score(cls):
print(f'{cls.top_name}是第一名,分数是{cls.top_score}')
# 定义静态方法
@staticmethod
def show_help():
print('学生成绩管理')
# 定义实例属性
def __init__(self,name):
self.name = name
# 定义实例方法
def record(self,score):
if Student.top_score < score:
Student.top_score = score
Student.top_name = self.name
if __name__ == '__main__':
# 打印帮助信息
Student.show_help()
# 创建实例对象
lihua = Student('李华')
zhangsan = Student('张三')
lisi = Student('李四')
# 记录学生分数
lihua.record(52.3)
zhangsan.record(56.3)
lisi.record(60)
# 打印最高分和他的名字
Student.show_top_score()
显示结果:
学生成绩管理
李四是第一名,分数是60
全文参考专栏面向对象部分。