python语言基础(五)--面向对象和三大特征
一、面向对象
1、概述
-
是一种编程思想,先把复杂的事情简单化,然后把程序员从执行者的角色变成指挥者的角色,更符合人们的思考习惯。
-
强调以对象为基础,完成各种操作,是基于面向过程的。
拓展:python同时具有面向过程和面向对象思想。
例如:平时生活中,我们说的车就是一种类,而我家楼下停的那辆牌照为XXX的车就是对象。
一般来说,生活中的方方面面都是对象,我们可以说万物皆对象。
2、三大特征简述
(一)、继承
概述
简单来说就是子类可以继承父类的某些成员,类似于现实中的子承父业。
格式
class 子类名(父类名):pass
好处
提高代码的复用性,提高代码的可维护性
弊端
耦合性增加
(二)、封装
概述
简单来说就是隐藏对象的相关属性和实现的细节,仅仅对外提供公共的访问方法
好处
提高代码的安全性
提高代码的复用性
弊端
代码量增加
(三)、多态
指的是同一个事物在不同条件下表现出的不同形态或者状态。在python中可以理解为函数输入不同的参数,返回不同的值和功能。
3、入门代码
相关概述
属性:名词,用来描述对象的外在特征,比如姓名,年龄等等
行为:动词,用来表示对象能做什么事情,比如学习,看书等等
类:抽象的概念,可以理解为一大类的东西,一种统称
对象:类的具体实现,有事物存在
格式
class 类名(父类名):def __init__(self):self.属性名字 = 数值def get...(self):具体的动作if __name__ == '__main__':tmp = 列名()print(tmp.属性名字)tmp.get...()
例如
# 需求: 定义1个汽车类, 然后具有跑的功能, 并调用.
# 1. 定义汽车类.
class Car():# 属性.def __init__(self):self.color = '红色'# 行为, 和以前定义的函数一样, 只不过第一个形参要写self.def run(self):print('汽车会跑!...')# 在main方法中测试调用.
if __name__ == '__main__':# 2. 创建汽车类对象.c1 = Car() # 大白话: 根据图纸制造了1辆汽车, 汽车名叫: c1c2 = Car() # 大白话: 根据图纸制造了1辆汽车, 汽车名叫: c2# 3. 调用 汽车类的 功能.md# 3.1 调用 属性# 3.2 调用 行为.c1.run()c2.run()# 4. 直接打印c1, c2 看看是什么.print(f'c1: {c1}') # 地址值print(f'c2: {c2}') # 地址值
4、self关键字介绍
概述
是python内置的一种属性,代表本类当前对象的应用,大白话来说就是哪个对象调用函数,函数内的self就代表哪个对象。
作用
用于函数内,区分不同的对象的,因为一个类会有不同的对象去调用它,self就是区分这些不同对象的。
例如
# 需求: 定义汽车类, 有run()的功能, 然后创建该类的对象, 并调用run()功能. 细节: run()要区分是哪个对象调用的.
# 1. 定义汽车类.
class Car:# 汽车类内部, 定义: 行为(函数), 表示具有: 跑的功能.def run(self):print('汽车具有 跑 的功能!')# 通过self关键字, 可以实现: 在函数内区分 到底是哪个对象 调用的该函数.print(f'当前对象为: {self}')# 在main函数中, 测试调用.
if __name__ == '__main__':# 2. 创建汽车类的对象.c1 = Car()c2 = Car()# 3. 调用Car类的 run()函数, 简写注释为: 调用 Car#run()c1.run()print('-' * 21)c2.run()print('-' * 21)# 4. 打印地址值.print(f'c1: {c1}')print(f'c2: {c2}')
5、属性的定义和调用
概述
属性=》名词,描述事物的外在特征,例如姓名,年龄等
定义格式
类内:对象名.属性名 = 属性值
类外:要结合 __ init __()魔法方法一起用
调用格式
类内:对象名.属性名
类外:self.属性名
注意
遇到的问题: 在main函数中, 通过 对象名.属性名 = 属性值的方式 给c1对象设置属性值了, 但是 c2对象没有属性值.
产生的原因: 在类外设置属性, 只能给当前对象设置, 不能给本类的其它对象设置.
解决方案: 通过 __ init __ ()魔法方法在类内给该类的对象设置属性值, 则该类所有的对象默认都有这些属性值.
代码
# 需求: 给车设置颜色为红色, 4个轮胎, 且获取属性值并输出.
# 扩展: 可以在类内部定义1个show()方法来获取刚才给车设置的属性.# 1. 定义汽车类.
class Car:# 2. 在类内定义show()方法, 获取属性值. 即: 类内获取属性值def show(self):# self = 本类当前对象的引用.print(f'颜色: {self.color}, 轮胎数: {self.num}')# 在main函数中测试调用.
if __name__ == '__main__':# 3. 创建对象.c1 = Car()# 4. 给c1对象设置属性值. 类外设置属性c1.color = '红色'c1.num = 4# 5. 打印c1对象的属性值. 类外获取属性值.print(f'颜色: {c1.color}, 轮胎数: {c1.num}')# 6. 调用show()方法, 获取属性值.c1.show()print('-' * 21)# 7. 创建c2汽车类对象, 尝试调用show()函数.c2 = Car()c2.show()
5、魔法方法
(一)、概述
概述
它是Python内置的一些函数, 主要是用来对类的功能做增强的
格式
__方法名__()
特点
在满足特定情况的场景下, 会被自动调用, 无需用户手动调用
常用的魔法方法
__ init __ () 在创建对象的时候, 会被自动调用, 一般用于给对象初始化一些属性值. 即: 对象名 = 类名() 就会自动调用该魔法方法.
__ str __ ()
__ del __()
(二)、init
(1)、无参数
概述
属性 = 名词, 即: 描述事物的外在特征的. 例如: 姓名, 年龄, 性别…
定义格式
类外: 对象名.属性名 = 属性值
类内: 要结合 __ init __ () 魔法方法一起使用.
调用格式
类外: 对象名.属性名
类内: self.属性名
例如
# 需求: 给车这个对象默认设置 颜色 color=黑色, 轮胎数 number=3
# 1. 定义汽车类.
class Car():# 2. 通过 魔法方法 init, 初始化 汽车对象的属性值, 即: 所有汽车类对象, 默认都有这个属性值.def __init__(self): # 无参数的 init函数.print('我是init函数, 我被调用了!')# 具体的设置属性值的动作.self.color = '黑色'self.number = 3# 3. 定义函数show(), 在类内调用 属性.def show(self):print(f'颜色: {self.color}, 轮胎数: {self.number}')# 在main函数中测试.
if __name__ == '__main__':# 4. 创建汽车类对象.c1 = Car()c2 = Car()print('-' * 21)# 扩展: 修改c1的属性值.c1.color = '霞光紫'c1.number = 10# # 5. 调用show()函数, 打印属性值.c1.show()print('-' * 21)c2.show()print('-' * 21)# 6. 扩展, 在类外调用属性.print(c1.color, c1.number)print(c2.color, c2.number)
(2)、有参数
回顾刚才的代码:
init魔法方法中的 属性值, 都是固定的, 写死的, 很不方便, 我们就思考, 这些参数值能不能通过 外部传参的方式来实现对其进行赋值操作呢?
答案:
肯定是可以的, 通过 带参数的init函数即可解决.
例如
# 需求: 通过外部传参的方式, 给汽车对象设置属性值, 例如: 褐色, 6个轮胎.
# 1. 定义汽车类.
class Car():# 2. 通过 魔法方法 init, 初始化 汽车对象的属性值, 即: 所有汽车类对象, 默认都有这个属性值.def __init__(self, color, number): # 带参数的 init函数.# def __init__(self, color = None, number = None): # 带参数的 init函数.print('我是init函数, 我被调用了!')# 具体的设置属性值的动作.self.color = colorself.number = number# 3. 定义函数show(), 在类内调用 属性.def show(self):print(f'颜色: {self.color}, 轮胎数: {self.number}')# 在main函数中测试.
if __name__ == '__main__':# 4. 创建汽车类对象.c1 = Car('黑色', 6) # 因为init函数带参, 所以需要传参.c2 = Car()print('-' * 21)# 扩展: 修改c1的属性值.c1.color = '霞光紫'c1.number = 10# # 5. 调用show()函数, 打印属性值.c1.show()print('-' * 21)c2.show()print('-' * 21)# 6. 扩展, 在类外调用属性.print(c1.color, c1.number)print(c2.color, c2.number)
(三)、str
概述
__ str __ () 当输出语句直接打印对象的时候, 会自动调用该魔法方法, 默认打印的是地址值, 无意义, 一般会改为: 打印该对象的各个属性值
解释
在调用print打印对象时,会打印一次,该函数可以定义输出函数。
例如
# 1. 定义汽车类, 属性: 品牌, 价格.
class Car():# 定义init, 初始化属性.def __init__(self, brand, price):self.brand = brand # 品牌self.price = price # 价格# 行为def run(self):print('汽车会跑!')# 魔法方法, strdef __str__(self):# return '我是str魔法方法'return f'品牌: {self.brand}, 价格: {self.price}'# 在main函数中测试调用.
if __name__ == '__main__':# 2. 创建汽车类对象.c1 = Car('小米Su7', 239999) # 创建对象时, 自动调用了: init魔法方法# 3. 直接打印c1对象print(c1) # 默认调用了 __str__() 魔法方法.
(四)、del
概述
__ del __ () 当手动删除对象时 或者 文件执行结束后, 该函数会被自动调用
解释
在删除对象,或者程序结束时,会调用该函数。
例如
# 1. 定义汽车类, 属性: 品牌, 价格.
class Car():# 定义init, 初始化属性.def __init__(self, brand, price):self.brand = brand # 品牌self.price = price # 价格# 行为def run(self):print('汽车会跑!')# 魔法方法, deldef __del__(self):print(f'对象 {self} 被删除了!')# 在main函数中测试调用.
if __name__ == '__main__':# 2. 创建汽车类对象.c1 = Car('小米Su7', 239999) # 创建对象时, 自动调用了: init魔法方法# 3. 删除c1对象, 看看结果.del c1
二、三大特征
1、继承
(一)、概述
概述
-
实际开发中, 我们发现好多类中的部分内容是相似的, 或者相同的, 每次写很麻烦, 针对于这种情况, 我们可以把这些相似(想同)的部分抽取出来,
-
单独的放到1个类中(父类), 然后让那多个类(子类) 和这个类产生关系, 这个关系就叫: 继承.
大白话
子承父业, Python中的继承, 子类 => 继承父类的 属性, 行为
格式
class 子类名(父类名):pass
好处
- 提高代码的复用性
- 提高代码的可维护性.
弊端
耦合性增强了. 父类"不好"的内容, 子类想没有都不行
细节
所有的类都直接或者间接继承自object, 它是所有类的父类, 也叫: 顶级类
(二)、单继承
概述
一个子类只有一个父类~
代码
# 1. 创建1个师傅类, 充当父类.
class Master(object):# 1.1 定义父类的 属性.def __init__(self):self.kongfu = '[古法煎饼果子配方]'# 1.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 2. 定义徒弟类, 继承自师傅类.
class Prentice(Master):pass# 在main函数中测试
if __name__ == '__main__':# 3. 创建子类的对象p = Prentice()# 4. 尝试打印 p对象的 属性 和 行为print(f'从父类继承的属性: {p.kongfu}')p.make_cake() # 从父类继承来的行为.
(三)、多继承
概述
一个子类有多个父类
细节
-
Python中支持多继承写法, 即: 1个类可以有多个父类, 写法为: class 子类名(父类名1, 父类名2…)
-
多继承关系中, 子类可以继承所有父类的属性和行为. 前提: 父类的私有成员除外.
-
多继承关系中, 多个父类如果有重名属性或者方法时, 子类会优先使用第1个父类(即: 最前边的父类)的该成员.
-
上述的继承关系, 我们可以通过 Python内置的 mro属性 或者 mro()方法来查看.
mro: Method Resolution Order, 即: 方法的解析顺序(调用顺序)
例如
# 1. 创建1个师傅类, 充当父类.
class Master(object):# 1.1 定义父类的 属性.def __init__(self):self.kongfu = '[古法煎饼果子配方]'self.name = 'Master'# 1.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 2. 创建1个师傅类, 充当父类.
class School(object):# 2.1 定义父类的 属性.def __init__(self):self.kongfu = '[海鸥煎饼果子配方]'# 2.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):pass# 在main函数中测试
if __name__ == '__main__':# 4. 创建子类的对象p = Prentice()# 5. 尝试打印 p对象的 属性 和 行为print(f'从父类继承的属性: {p.kongfu}')p.make_cake() # 从父类继承来的行为.print('-' * 21)# 6. 演示方法的解析顺序, 即: MRO, 看看方法优先会从哪些类中找.print(Prentice.__mro__) # 输出封装成: 元组print(Prentice.mro()) # 输出封装成: 列表
(四)、多层继承
概述
类A继承类B, 类B继承类C
例如
# 1. 创建1个师傅类, 充当父类.
class Master(object):# 1.1 定义父类的 属性.def __init__(self):self.kongfu = '[古法煎饼果子配方]'self.name = 'Master'# 1.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 2. 创建1个师傅类, 充当父类.
class School(object):# 2.1 定义父类的 属性.def __init__(self):self.kongfu = '[海鸥煎饼果子配方]'# 2.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):# 3.1 定义本类(子类)的 属性.def __init__(self):self.kongfu = '[独创煎饼果子配方]'# 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.def make_cake(self): # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.print(f'使用 {self.kongfu} 制作煎饼果子')# 3.3 定义函数 make_master_cake(), 表示: 古法摊煎饼果子配方.def make_master_cake(self):# 前提(细节): 需要重新初始化一下父类的 属性.Master.__init__(self)# 调用Master#make_cake()Master.make_cake(self)# 3.4 定义函数 make_school_cake(), 表示: 海鸥摊煎饼果子配方.def make_school_cake(self):# 前提(细节): 需要重新初始化一下父类的 属性.School.__init__(self)# 调用School#make_cake()School.make_cake(self)# 4. 定义徒孙类, 继承: 徒弟类.
class TuSun(Prentice): # 继承关系: TuSun => Prentice => School, Master => objectpass# 在main函数中测试
if __name__ == '__main__':# 4. 创建 徒孙类 的对象ts = TuSun()# 5. 调用父类的成员.print(f'属性: {ts.kongfu}') # 独创煎饼果子配方ts.make_cake() # 独创煎饼果子配方ts.make_master_cake() # 古法ts.make_school_cake() # 海鸥
(五)、方法重写
概述
子类出现和父类重名的属性, 方法时, 会覆盖父类中的成员, 这种写法就称之为: 重写, 也叫: 覆盖
注意
重写一般特指: 方法重写
场景
当子类需要沿袭父类的功能, 而功能主体又有自己额外需求的时候, 就可以考虑使用方法重写了
细节
子类有和父类重名的属性和方法时, 优先使用 子类的成员. 就近原则
例如
# 1. 创建1个师傅类, 充当父类.
class Master(object):# 1.1 定义父类的 属性.def __init__(self):self.kongfu = '[古法煎饼果子配方]'self.name = 'Master'# 1.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 2. 创建1个师傅类, 充当父类.
class School(object):# 2.1 定义父类的 属性.def __init__(self):self.kongfu = '[海鸥煎饼果子配方]'# 2.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):# 3.1 定义本类(子类)的 属性.def __init__(self):self.kongfu = '[独创煎饼果子配方]'# 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.def make_cake(self): # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.print(f'使用 {self.kongfu} 制作煎饼果子')# 在main函数中测试
if __name__ == '__main__':# 4. 创建子类的对象p = Prentice()# 5. 尝试打印 p对象的 属性 和 行为print(f'属性: {p.kongfu}')p.make_cake()print('-' * 21)# 6. 演示方法的解析顺序, 即: MRO, 看看方法优先会从哪些类中找.print(Prentice.__mro__) # 输出封装成: 元组print(Prentice.mro()) # 输出封装成: 列表
(六)、子类访问父类成员
QA
问: 重写后, 子类如何访问父类的成员呢?
答案:
格式1: 父类名.父类方法名(self)
格式2: super().父类方法()
(1)父类名.父类方法名(self)
例如
# 1. 创建1个师傅类, 充当父类.
class Master(object):# 1.1 定义父类的 属性.def __init__(self):self.kongfu = '[古法煎饼果子配方]'self.name = 'Master'# 1.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 2. 创建1个师傅类, 充当父类.
class School(object):# 2.1 定义父类的 属性.def __init__(self):self.kongfu = '[海鸥煎饼果子配方]'# 2.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):# 3.1 定义本类(子类)的 属性.def __init__(self):self.kongfu = '[独创煎饼果子配方]'# 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.def make_cake(self): # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.print(f'使用 {self.kongfu} 制作煎饼果子')# 3.3 定义函数 make_master_cake(), 表示: 古法摊煎饼果子配方.def make_master_cake(self):# 前提(细节): 需要重新初始化一下父类的 属性.Master.__init__(self)# 调用Master#make_cake()Master.make_cake(self)# 3.4 定义函数 make_school_cake(), 表示: 海鸥摊煎饼果子配方.def make_school_cake(self):# 前提(细节): 需要重新初始化一下父类的 属性.School.__init__(self)# 调用School#make_cake()School.make_cake(self)# 在main函数中测试
if __name__ == '__main__':# 4. 创建子类的对象p = Prentice()# 5. 尝试打印 p对象的 属性 和 行为print(f'属性: {p.kongfu}') # 独创煎饼果子配方p.make_cake() # 独创煎饼果子配方print('-' * 21)# 6. 调用父类 Master类的 古法煎饼果子配方p.make_master_cake() # 古法print('-' * 21)# 7. 调用父类 School类的 古法煎饼果子配方p.make_school_cake() # 海鸥
(2)super().父类方法()
概述
它类似于self, 只不过: self代表本类当前对象的引用. super代表本类对象 父类的引用
大白话
self = 自己, super = 父类
作用
初始化父类成员, 实现 在子类中访问父类成员的
细节
- super()在多继承关系中, 只能初始化第1个父类的成员, 所以: super()更适用于 单继承环境.
- 多继承关系中, 如果想实现精准初始化(操作)某个父类的成员, 可以通过 父类名.父类方法名(self)
- 在单继承关系中, 用 super() 可以简化代码
例如
# 1. 创建1个师傅类, 充当父类.
class Master(object):# 1.1 定义父类的 属性.def __init__(self):self.kongfu = '[古法煎饼果子配方]'# 1.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 2. 创建1个师傅类, 充当父类.
class School(object):# 2.1 定义父类的 属性.def __init__(self):self.kongfu = '[海鸥煎饼果子配方]'# 2.2 定义父类的 行为, 表示: 摊煎饼.def make_cake(self):print(f'使用 {self.kongfu} 制作煎饼果子')# 3. 定义徒弟类, 继承自师傅类.
class Prentice(School, Master):# 3.1 定义本类(子类)的 属性.def __init__(self):self.kongfu = '[独创煎饼果子配方]'# 3.2 定义本类(子类)的 行为, 表示: 摊煎饼.def make_cake(self): # 子类出现和父类重名且一模一样的函数, 称之为: 方法重写.print(f'使用 {self.kongfu} 制作煎饼果子')# 3.3 定义函数 make_old_cake(), 表示: 父类的摊煎饼果子配方.def make_old_cake(self):# 前提(细节): 需要重新初始化一下父类的 属性.super().__init__()# 调用 父类的#make_cake()# Master.make_cake(self) # 格式1: 父类名.父类方法名(self)super().make_cake() # 格式2: super().父类方法名()# 在main函数中测试
if __name__ == '__main__':# 4. 创建子类的对象p = Prentice()# 5. 尝试打印 p对象的 属性 和 行为print(f'属性: {p.kongfu}') # 独创煎饼果子配方p.make_cake() # 独创煎饼果子配方print('-' * 21)# 6. 调用父类的 煎饼果子配方p.make_old_cake() # 海鸥
2、封装
(一)、私有化属性
概述
封装指的是 隐藏对象的属性 和 实现细节, 仅对外提供公共的访问方式
问1: 怎么隐藏 对象的属性 和 实现细节(函数)?答: 通过 私有化解决.
问2: 公共的访问方式是什么?答: get_xxx(), set_xxx()函数.
问3: get_xxx()和set_xxx()函数 必须成对出现吗?答: 不一定, 看需求, 如果只获取值就用 get_xxx(), 如果只设置值就用 set_xxx(). 如果需求不明确, 建议都写.
问4: 封装指的就是 私有, 这句话对吗?答: 不对, 因为我们常用的函数也是封装的一种体现
好处
- 提高代码的安全性. 通过 私有化 实现的.
- 提高代码的复用性. 通过 函数 实现的.
弊端
代码量增量了, 封装的代码量会变多.
这里的代码量增加指的是: 私有化以后, 就要提供公共的访问方式, 私有化内容越多, 公共的访问方式就越多, 代码量就越多
格式
__属性名 # 注意: 这里是 两个_
__函数名()
特点
只能在本类中直接访问, 外界无法直接调用
例如
# 1. 定义徒弟类, 有自己的属性 和 行为.
class Prentice(object):# 1.1 属性def __init__(self):self.kongfu = '[独创的煎饼果子配方]'# 私有的属性.# self.__money__ = 500000 # 这个不是私有, 就是变量名叫: __money__self.__money = 500000 # 这个才是私有化的写法.# 1.2 对外提供公共的访问方式, 可以实现: 获取私有的变量, 以及给变量设置值.# 获取值.def get_money(self):return self.__money# 设置值def set_money(self, money):# 可以在这里对 money属性做判断, 但是没必要. 因为Python属于后端代码, 这里的钱肯定是前端传过来的, 而传过来的数据已经经过了前端的校验.# 换言之, 这里如果校验就属于 二次校验了. 实际开发中, 重要字段会做二次校验, 否者可以不做校验.# if money > 0:# self.__money = money# else:# self.__money = 0self.__money = money# 1.3 行为def make_cake(self):print(f'采用 {self.kongfu} 制作煎饼果子!')# 验证: 私有成员, 在本类中是可以直接访问的.print(f'私房钱为: {self.__money}')# 2. 定义徒孙类, 继承自徒弟类.
class TuSun(Prentice):pass# 在main函数中测试调用
if __name__ == '__main__':# 3. 创建徒孙类对象.ts = TuSun()# 4. 尝试访问父类的成员.# 父类的公共的 属性 和 行为.print(f'父类的属性: {ts.kongfu}')ts.make_cake()print("-" * 21)# 父类的 私有的 属性.# print(f'父类的私有属性: {ts.money}') # 报错, AttributeError# print(f'父类的私有属性: {ts.__money}') # 报错, AttributeErrorprint(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')# 通过父类的公共方式, 修改 父类的私有属性.ts.set_money(10)print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')
(二)、私有化方法介绍
目的
演示 私有化方法. 即: 父类的私有化方法, 也需要提供1个公共的访问方式, 让子类来访问
QA
问: 什么时候使用私有化呢?
答: 父类的成员不想被子类直接继承(或者重写, 修改等), 但是还想给子类用, 就可以考虑用私有化.
例如
# 1. 定义徒弟类, 有自己的属性 和 行为.
class Prentice(object):# 1.1 属性def __init__(self):self.kongfu = '[独创的煎饼果子配方]'# 私有的属性.# self.__money__ = 500000 # 这个不是私有, 就是变量名叫: __money__self.__money = 500000 # 这个才是私有化的写法.# 1.2 对外提供公共的访问方式, 可以实现: 获取私有的变量, 以及给变量设置值.# 获取值.def get_money(self):return self.__money# 设置值def set_money(self, money):self.__money = money# 1.3 行为def __make_cake(self):print(f'采用 {self.kongfu} 制作煎饼果子!')# 验证: 私有成员, 在本类中是可以直接访问的.print(f'私房钱为: {self.__money}')# 针对于父类的私有方法, 提供公共的访问方式(在其内部调用 私有化的方法即可)def my_make(self):# 调用私有化方法 __make_cake()self.__make_cake()# 2. 定义徒孙类, 继承自徒弟类.
class TuSun(Prentice):# 演示用, 在子类中 恶意的修改 父类的函数内容. 通过 方法重写 实现.# def make_cake(self):# print('加入调料: 砒霜')# print('加入调料: 鹤顶红')# print('加入调料: 含笑半步癫')# print('加入调料: 一日断肠散')# # 调用父类的方法.# super().make_cake()pass# 在main函数中测试调用
if __name__ == '__main__':# 3. 创建徒孙类对象.ts = TuSun()# 4. 尝试访问父类的成员.# 4.1 父类的 私有的 属性.print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')# 通过父类的公共方式, 修改 父类的私有属性.ts.set_money(10)print(f'父类的私有属性, 通过 公共的方式访问: {ts.get_money()}')print('-' * 21)# 4.2 父类的 私有的 方法(行为).# ts.__make_cake() # AttributeError, 父类私有成员(方法), 子类无法直接访问.# ts.make_cake()ts.my_make()
3、多态
概述
多态指的是同一个事物在不同时刻, 不同场景下表现出来的不同形态, 状态
大白话解释
Python中的多态: 同一个函数 接收不同的参数 会有不同的结果.
现实生活中的多态: 一杯水, 高温 => 气体, 常温 => 液体, 低温 => 固体
前提条件
- 要有继承关系. # 扩展: 没有继承关系也行, 因为Python是弱类型的, 对数据的类型限定不严格, 可以称之为 => 伪多态.
- 要有方法重写, 否则无意义.
- 要有父类引用指向子类对象.
好处
提高代码的可维护性. 即: 同样的一个函数, 未来需求变化了, 我们传入不同的参数即可, 无需修改源码, 既有不同的结果。开发原则, 对修改关闭, 对扩展开放. 大白话: 需求变化了, 不能该源码, 尽量加代码。
弊端
不知道传入的是哪一个具体的子类, 所以无法直接访问子类的特有成员
例如
# 1. 定义父类, 动物类, 有个speak()函数.
class Animal(object):def speak(self):pass# 2. 定义子类, 狗类, 继承自动物类, 重写Animal#speak()函数.
class Dog(Animal):# 重写父类的speak()函数def speak(self):print('汪汪汪!')# 3. 定义子类, 猫类, 继承自动物类, 重写Animal#speak()函数.
class Cat(Animal):def speak(self):print('喵喵喵!')def catch_mouse(self):print('猫会抓老鼠!')# 4.假设需求变化, 增加了 猴子类.
class Monkey(Animal):def speak(self):print('桀桀桀!')# 验证Python是伪多态, 即: print_animal()函数, 不传入Animal的子类的对象, 也行.
class Phone:def speak(self):print('手机叫一次, 你要唱一首歌!')# 5. 定义函数, 接收Animal类型, 调用speak()函数.
# def print_animal(an): # an:Animal = Dog(), an:Animal = Cat(), 父类引用指向子类对象.
def print_animal(an: Animal): # an:Animal = Dog(), an:Animal = Cat(), 父类引用指向子类对象.an.speak()# an.catch_mouse() 不能访问子类的 特有成员.# 6. 在main函数中测试
if __name__ == '__main__':# 6.1 创建猫对象, 狗对象.cat = Cat()dog = Dog()mon = Monkey()# 6.2 调用print_animal()函数.# 发现: 同一个函数, 接受不同的对象, 实现效果不一样 => 多态.print_animal(cat)print('-' * 21)print_animal(dog)print('-' * 21)print_animal(mon)print('-' * 21)# 7. 调用print_animal()函数, 传入 非Animal类的子类.phone = Phone()print_animal(phone)
三、其他特征
1、抽象类
概述
有抽象方法的类就叫 抽象类, 也可以称之为: 接口
抽象方法
没有方法体的方法, 叫: 抽象方法, 即: 方法体是用 pass 来编写的
目的
抽象类: 一般充当父类, 用于制定: 标准.
子类: 普通类 继承抽象类, 重写抽象方法, 提供具体的实现即可
例如
# 1. 定义AC类(空调类, 抽象类), 表示: 空调的标准.
class AC(object):# 1.1 制冷, 抽象方法(没有方法体的方法)def cool_wind(self):pass# 1.2 热风def hot_wind(self):pass# 1.3 左右摆风def swing_l_r(self):pass# 2. 定义Gree类(格力空调类), 继承: AC类.
class Gree(AC):# 2.1 重写 AC#cool_wind 方法def cool_wind(self):print('格力空调 核心制冷技术 制作冷风')# 2.2 重写 AC#hot_wind 方法def hot_wind(self):print('格力空调 核心制热技术 制作热风')# 2.3 重写 AC#swing_l_r 方法def swing_l_r(self):print('格力空调 左右摆风!')# 3. 定义Media类(美的空调类), 继承: AC类.
class Media(AC):# 3.1 重写 AC#cool_wind 方法def cool_wind(self):print('美的空调 核心制冷技术 制作冷风')# 3.2 重写 AC#hot_wind 方法def hot_wind(self):print('美的空调 核心制热技术 制作热风')# 3.3 重写 AC#swing_l_r 方法def swing_l_r(self):print('美的空调 左右摆风!')# 定义函数, 测试空调的性能.
def my_ac(ac: AC):ac.cool_wind()ac.hot_wind()ac.swing_l_r()# 4. 在main函数中测试.
if __name__ == '__main__':# 非多态方式# 4.1 测试 格力空调.g = Gree()g.cool_wind()g.hot_wind()g.swing_l_r()print('-' * 21)# 4.2 测试 美的空调.m = Media()m.cool_wind()m.hot_wind()m.swing_l_r()print('-' * 21)# 5. 多态方式.my_ac(g)print('-' * 21)my_ac(m)
2、类属性和对象属性
(一)、对象属性
概述
属于 对象的属性, 即: 每个对象都有, 且A对象的属性值修改了 不会影响 B对象的属性值
定义格式
类外: 对象名.属性名 = 属性值
类内: 写到 init魔法方法中, self.属性名 = 属性值
调用格式
类外: 对象名.属性名
类内: self.属性名
(二)、类属性
概述
属于 类的属性, 即: 可以被 该类下所有的对象所共享. 即: 无论是谁修改了这个变量的值, 之后大家用的都是修改后的
定义格式
定义在类中, 方法外的位置, 写法和以前我们写变量的格式 一样
调用格式
方式1: 类名.属性名
方式2: 对象名.属性名 可以这样写, 但是不推荐
细节
修改类属性必须通过 类名.属性名 = 属性值 的方式来修改, 不能通过 对象名.属性名 = 属性值的方式来修改.
因为: 前者是在修改 类属性的值, 后者是在 给对象新增1个属性
(三)、代码
# 1. 定义学生类.
class Student:# 1.1 定义 类属性(类变量).teacher_name = '菩提'# 1.2 对象属性, 类内 设置.def __init__(self):self.name = '张三' # 对象属性, 该类的每个对象都有.# 在main函数中测试.
if __name__ == '__main__':# 2. 创建学生类对象.s1 = Student()s2 = Student()# 3. 对象属性, 在类外 设置 对象属性.s1.name = '李四's1.age = 21 # 只有s1对象有.# 4. 对象属性, 在类外 获取 对象属性.print(f'类外获取对象属性值: {s1.age}') # 21print(f'类外获取对象属性值: {s1.name}') # 李四print(f'类外获取对象属性值: {s2.name}') # 张三print("-" * 21)# 5. 在类外, 访问类属性# 方式1: 类名. 的方式print(Student.teacher_name) # 菩提# 方式2: 对象名. 的方式, 可以, 但是不推荐.print(s1.teacher_name) # 菩提print(s2.teacher_name) # 菩提print("-" * 21)# 6. 修改类属性的值.Student.teacher_name = '唐僧' # 可以修改 类属性值# s1.teacher_name = '唐僧' # 不是在修改类变量的值, 而是在给s1对象新增1个属性值.# 7. 重新打印 类属性的值.# 方式1: 类名. 的方式print(Student.teacher_name) # 唐僧# 方式2: 对象名. 的方式, 可以, 但是不推荐.print(s1.teacher_name) # 唐僧print(s2.teacher_name) # 唐僧
3、类方法和静态方法
(一)、类方法
概述
它表示属于类的方法, 可以被所有的对象共享
细节
-
必须用装饰器 @classmethod 来修饰.
-
第一个参数必须是 cls, 表示: 当前类的 引用, 即: 等价于 类名. 的形式
-
类方法可以被 对象名. 或者 类名. 的方式来调用, 推荐使用 后者.
(二)、静态方法
概述
它表示属于 所有对象所共享 的方法, 可以被所有的对象共享
细节
-
必须用装饰器 @staticmethod 来修饰.
-
无参数要求, 根据需求来即可, 可传可不传.
-
静态方法可以被 对象名. 或者 类名. 的方式来调用, 推荐使用 后者.
QA
问: 静态方法 和 类方法的区别?
答:
1. 用的装饰器不同.
类方法: @classmethod
静态方法: @staticmethod
1. 是否必须要传 第1个参数.
类方法: 必须传cls参数, 表示 当前类的引用.
静态方法: 根据需求来即可, 可传可不传.
问: 以后到底怎么选择用 静态方法 还是 类方法呢?
答:
看需求, 如果某个方法时被所有对象所共享, 就可以考虑用 静态 或者 类方法.
再看是否要使用 cls参数, 用 => 类方法, 不用 => 静态方法.
(三)、代码
# 1. 定义学生类.
class Student:name = '码头' # 类属性def __init__(self):self.age = 18 # 对象属性def method(self):print('我是method函数, 普通方法')@staticmethoddef method2():print('我是method2方法, 我是静态方法')print(f'调用类属性: {Student.name}')@classmethoddef method3(cls): # cls来源于 class单词# self = 本类对象的引用, 类似于 对象名.# cls = 本类的引用, 类似于: 类名.print('我是method3方法, 我是类方法')print(f'cls表示类属性, 内容为: {cls}') # <class '__main__.Student'>print(f'调用类属性: {cls.name}')# 在main函数中测试.
if __name__ == '__main__':# 2. 创建对象.s1 = Student()# 3. 访问 静态方法Student.method2() # 方式1: 类名.s1.method2() # 方式2: 对象名.print('-' * 21)# 4. 访问 类方法Student.method3() # 方式1: 类名.s1.method3() # 方式2: 对象名.