面向对象
面向对象就是设计一个类,基于类
创建对象
,并使用创建出来的类完成具体的工作
面向对象的三大特性:封装
、继承
、多态
面向对象基本概述:
属性: 名词, 用来描述事物的外在特征的, 例如: 姓名, 性别, 年龄, 身高, 体重...
行为: 动词, 表示事物能够做什么, 例如: 学习, 吃, 睡...
类: 抽象的概念, 看不见, 摸不着. 类 = 属性 + 行为
对象: 类的具体体现, 实现.
定义类的格式:
class 类名:
# 属性, 就和以前定义变量一样.
# 行为, 就和以前定义函数一样, 只不过第一个形参要写self
如何使用类中的成员:
1. 创建该类的对象.
对象名 = 类名()
2. 通过 对象名. 的方式来调用.
对象名.属性
对象名.行为()
1. class(类)
初识对象
使用对象组织数据:先 创建类(class),类名加括号便是创建对象,最后对象属性赋值
# 设计一个类(类比生活,登记表)
class PersonTable:
name = None
# 设计对象(打印登记表)
t1 = PersonTable()
# 对象属性进行赋值
t1.name = 'bob'
# 获取对象中记录的信息
print(t1.name)
类的成员
class
是关键字,表示要定义类了
类的属性(数据),即定义在类中的变量(成员变量)
类的行为(函数),即定义在类中的函数(成员方法)
定义在类外面的函数叫做函数,类内部的函数叫做方法
# 成员方法的定义语法
def 方法名(self, 形参1, ..., 形参N):
方法体
方法定义中 self
必须填写,用来表示类对象自身的意思,谁调用self, self就是谁。
当我们使用类对象
调用方法时,self会自动被python传入,在方法内部想要访问类的成员变量,必须使用 self
类和对象
类只是一种程序内的 ’设计图纸‘ ,需要基于图纸生产实体(对象),才能正常工作,称为:面向对象编程
魔法方法
魔法方法
在创建类对象(构造类)的时候:
(1) 会自动执行
(2)将传入参数自动传递给构造方法使用
其他内置方法(魔术方法)
_ _init__是python类内置的方法之一,内置的类方法,各有各自特殊的方法,这些内置方法称之为:魔术方法
常用的魔法方法:
__init__() 在创建对象的时候, 会被自动调用, 一般用于给对象初始化一些属性值. 即: 对象名 = 类名() 就会自动调用该魔法方法.
__str__() 当输出语句直接打印对象的时候, 会自动调用该魔法方法, 默认打印的是地址值, 无意义, 一般会改为: 打印该对象的各个属性值.
__del__() 当手动删除对象时 或者 文件执行结束后, 该函数会被自动调用.
可以理解为:
init魔法方法: 创建1个对象 = 调用一次
str魔法方法: 打印一次对象 = 调用一次
del魔法方法: 只要删除1个该类的对象 = 调用一次
# 小于、大于符号比较
__lt__
# 小于等于、大于等于符号比较
__le__
# ==符号比较
__eq__
2. 封装
封装的概念:将现实世界事物在类中描述为 属性和方法,即为封装
类中的部分属性和行为不对外公开,称为私有成员
,类对象无法访问私有成员,但类中的其他成员可以访问私有成员
封装介绍:
概述:
封装指的是 隐藏对象的属性 和 实现细节, 仅对外提供公共的访问方式.
问1: 怎么隐藏 对象的属性 和 实现细节(函数)?
答: 通过 私有化解决.
问2: 公共的访问方式是什么?
答: get_xxx(), set_xxx()函数.
问3: get_xxx()和set_xxx()函数 必须成对出现吗?
答: 不一定, 看需求, 如果只获取值就用 get_xxx(), 如果只设置值就用 set_xxx(). 如果需求不明确, 建议都写.
问4: 封装指的就是 私有, 这句话对吗?
答: 不对, 因为我们常用的函数也是封装的一种体现.
好处:
1. 提高代码的安全性. 通过 私有化 实现的.
2. 提高代码的复用性. 通过 函数 实现的.
弊端:
代码量增量了, 封装的代码量会变多.
这里的代码量增加指的是: 私有化以后, 就要提供公共的访问方式, 私有化内容越多, 公共的访问方式就越多, 代码量就越多.
私有化介绍:
格式:
__属性名 # 注意: 这里是 两个_
__函数名()
特点:
只能在本类中直接访问, 外界无法直接调用.
私有化方法. 即: 父类的私有化方法, 也需要提供1个公共的访问方式, 让子类来访问.
问: 什么时候使用私有化呢?
答: 父类的成员不想被子类直接继承(或者重写, 修改等), 但是还想给子类用, 就可以考虑用私有化.
以下是一个简单的案例
# 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. 继承
继承的概念:一个类继承另外一个类的成员变量和方法
单继承
: 一个类继承另一个类,
多继承
:一个类继承多个类,按照顺序从左向右一次继承
多继承中如果 父类有同名的属性或方法 ,先继承的优先级高于后继承
pass
:普通的占位语句,用来保证属性、方法或类定义的完整性,表示无内容,空的意思(不写新的属性和方法,只是为了语法不报错)
复写
:子类继承父类的成员属性和方法后,如不满意,可复写,即:在子类用重新定义同名的属性或方法
注:只可以在子类内部调用父类的同名成员,子类的实体类对象调用默认是子类复写后的
"""
继承相关概述:
概述:
实际开发中, 我们发现好多类中的部分内容是相似的, 或者相同的, 每次写很麻烦, 针对于这种情况, 我们可以把这些相似(想同)的部分抽取出来,
单独的放到1个类中(父类), 然后让那多个类(子类) 和这个类产生关系, 这个关系就叫: 继承.
可以理解为:
子承父业, Python中的继承, 子类 => 继承父类的 属性, 行为.
格式:
class 子类名(父类名):
pass
好处:
1. 提高代码的复用性.
2. 提高代码的可维护性.
弊端:
耦合性增强了. 父类"不好"的内容, 子类想没有都不行.
叫法:
子类: 也叫 派生类, 扩展类.
父类: 也叫 基类, 超类.
object:
所有的类都直接或者间接继承自object, 它是所有类的父类, 也叫: 顶级类.
"""
# 案例: 父类有默认性别男, 爱好散步行走, 定义子类继承父类, 看是否可以访问这些属性 和 行为.
# 1. 定义父类.
class Father:
# 1.1 定义父类的属性.
def __init__(self):
self.gender = '男'
# 1.2 定义父类的行为
def walk(self):
print('饭后走一走, 能活九十九!')
# 2. 定义子类, 继承自父类.
class Son(Father):
pass
# 在main中测试
if __name__ == '__main__':
# 3. 创建子类的对象.
s = Son()
# 4. 尝试打印 s对象的 属性 和 行为
print(f'性别: {s.gender}')
s.walk()
# 一旦复写父类成员,调用类对象时会调用复写后的新成员,需要使用特殊方式调用
1. 父类名.成员变量 父类名.成员方法(self)
2. super().成员变量 super().成员方法()
3.1 继承-多继承
- Python中支持多继承写法, 即: 1个类可以有多个父类, 写法为: class 子类名(父类名1, 父类名2…)
- 多继承关系中, 子类可以继承所有父类的属性和行为. 前提: 父类的私有成员除外.
- 多继承关系中, 多个父类如果有重名属性或者方法时, 子类会优先使用第1个父类(即: 最前边的父类)的该成员.
- 上述的继承关系, 我们可以通过 Python内置的 mro属性 或者 mro()方法来查看.
mro: Method Resolution Order, 即: 方法的解析顺序(调用顺序)
3.2 继承-方法重写
概述:
子类出现和父类重名的属性, 方法时, 会覆盖父类中的成员, 这种写法就称之为: 重写, 也叫: 覆盖.
注意:
重写一般特指: 方法重写
.
应用场景:
当子类需要沿袭父类的功能, 而功能主体又有自己额外需求的时候, 就可以考虑使用方法重写了.
"""
子类有和父类重名的属性和方法时, 优先使用 子类的成员. 就近原则.
问: 重写后, 子类如何访问父类的成员呢?
答:
格式1: 父类名.父类方法名(self)
格式2: super().父类方法()
super关键字介绍:
概述:
它类似于self, 只不过: self代表本类当前对象的引用. super代表本类对象 父类的引用.
可以理解为:
self = 自己, super = 父类
作用:
初始化父类成员, 实现 在子类中访问父类成员的.
细节:
1. super()在多继承关系中, 只能初始化第1个父类的成员, 所以: super()更适用于 单继承环境.
2. 多继承关系中, 如果想实现精准初始化(操作)某个父类的成员, 可以通过 父类名.父类方法名(self)
3. 在单继承关系中, 用 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 = '[黑马AI煎饼果子配方]'
# 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() # 黑马AI
3.3 多层继承
概述:
实际开发中, 类与类之间是可以多层继承的, 例如: 类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 = '[黑马AI煎饼果子配方]'
# 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(), 表示: 黑马AI摊煎饼果子配方.
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() # 黑马AI
4. 多态
概念:多态指的是:多种状态,即同样的行为(函数),传入不同的 对象,得到不同的状态
多态常作用在继承关系上。函数(方法)形参声明接收父类对象,实际传入父类的对象进行工作,即:以父类做定义声明,以子类做实际工作,用以获得同一行为,不同状态
抽象类
父类用来确定有哪些方法,具体的方法实现由子类自行决定,这种写法叫做抽象类(接口),含有抽象方法的类称为抽象类,方法体是空实现的(pass)称为抽象方法
抽象类的作用:多用于做顶层设计,以便子类做具体实现,也是对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法,并配合多态使用,获得不同的工作状态
"""
多态:
概述:
多态指的是同一个事物在不同时刻, 不同场景下表现出来的不同形态, 状态.
可以理解为:
Python中的多态: 同一个函数 接收不同的参数 会有不同的结果.
现实生活中的多态: 一杯水, 高温 => 气体, 常温 => 液体, 低温 => 固体.
前提条件:
1. 要有继承关系. # 扩展: 没有继承关系也行, 因为Python是弱类型的, 对数据的类型限定不严格, 可以称之为 => 伪多态.
2. 要有方法重写, 否则无意义.
3. 要有父类引用指向子类对象.
多态的好处:
提高代码的可维护性. 即: 同样的一个函数, 未来需求变化了, 我们传入不同的参数即可, 无需修改源码, 既有不同的结果.
扩展: 开发原则, 对修改关闭, 对扩展开放. 大白话: 需求变化了, 不能该源码, 尽量加代码.
多态的弊端:
不知道传入的是哪一个具体的子类, 所以无法直接访问子类的特有成员.
多态在实际开发中的应用场景:
父类型作为方法形参的数据类型, 这样可以接受其任意的子类对象, 实现传入什么子类对象, 就调用其对应的功能.
需求:
构建对象对战平台object_play,
1. 英雄一代战机(战斗力60)与敌军战机(战斗力70)对抗。英雄1代战机失败!
2. 卧薪尝胆,英雄二代战机(战斗力80)出场!,战胜敌军战机!
3. 对象对战平台object_play, 代码不发生变化的情况下, 完成多次战斗
分析流程:
抽象战机类 HeroFighter AdvHeroFighter;敌机EnemyFighter;
构建对象战斗平台, 使用多态实现
"""
# 1. 构建 1代 英雄机.
class HeroFighter(object):
def power(self):
return 60 # 战斗力: 60
# 2. 构建 2代 英雄机.
class AdvHeroFighter(HeroFighter):
def power(self):
return 80 # 战斗力: 80
# 3. 构建 敌机.
class EnemyFighter(object):
def power(self):
return 70 # 战斗力: 70
# 4. 搭建对战平台, 即: 通用的函数, 接收不同的参数, 效果不同, 但是函数还是那个函数.
def object_play(hf:HeroFighter, ef:EnemyFighter):
# 具体的战斗过程
if hf.power() > ef.power():
print('英雄机获得胜利!')
elif hf.power() < ef.power():
print('敌机惨胜!')
else:
print('平局!')
# 5. 在main函数中测试.
if __name__ == '__main__':
# 5.1 创建 各种战机.
hf1 = HeroFighter() # 1代英雄机
hf2 = AdvHeroFighter() # 2代英雄机
ef = EnemyFighter() # 敌机
# 5.2 具体的对战过程.
# Pythong中的多态: 同一个函数 接受不同的参数 实现不同的效果 => 多态
object_play(hf1, ef) # 1代英雄机 和 敌机
print('-' * 21)
object_play(hf2, ef) # 2代英雄机 和 敌机
类型注解
类型注解分为:
- 变量的类型注解
- 函数(方法)形参列表的类型注解
- 返回值的类型注解
类型注解只要帮助第三方IDE工具对代码进行类型推断,协助做代码提示,帮助开发者自身对变量类型注解(备注),并不会真正的对类型做验证和判断,类型注解仅仅是提示性的,不是决定性的
Union可以定义联合类型注解
# 变量类型注解基本语法
格式1: 变量:类型
格式2: 在注释中:# type:类型
# 函数(方法)形参进行类型注解语法
def 函数方法名(形参名: 类型, ... ):
pass
# 函数(方法)返回值进行类型注解
def 函数方法名(形参名: 类型, ... ) -> 返回值类型 :
pass
# Union 类型注释语法:Union[类型, ..., 类型]
from typing import Union
a: list[Union[str, int]] = [1, 2, 'itheima']
b: dict[str, Union[str, int]] = {'a': 'Jay', 'age': 19}
标签:__,Python,子类,self,多态,cake,父类,class,make
From: https://blog.csdn.net/m0_74893204/article/details/142965846