一、面向对象
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辆汽车, 汽车名叫: c1
c2 = 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 = color
self.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('汽车会跑!')
# 魔法方法, str
def __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('汽车会跑!')
# 魔法方法, del
def __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 => object
pass
# 在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 = 0
self.__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}') # 报错, AttributeError
print(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}') # 21
print(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函数, 普通方法')
@staticmethod
def method2():
print('我是method2方法, 我是静态方法')
print(f'调用类属性: {Student.name}')
@classmethod
def 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: 对象名.