P120 继承快速入门 2025/2/15
一、为什么需要继承
- 老方法:
- 问题的分析:
- pupil 和 graduate有很多相同的属性和方法
- 目前这样的做法,代码复用性差
- 同时也不利于代码的维护和管理
- 引出—>继承
二、基本介绍
-
继承介绍:
- 继承可以解决代码复用,让我们的编程更加靠近人类思维
- 当多个类存在相同的属性(成员变量)和方法时,可以从这些类中**抽象出父类,**在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法。
-
继承示意图:
- 继承基本语法:
class DerivedClassName(BaseClassName):
<statement-1>
·
·
·
<statement-N>
- 解读:
- 派生类就会自动拥有基类定义的属性和方法
- 基类习惯上也叫父类
- 派生类习惯上也叫子类
三、快速入门
- 使用继承编写的代码:
# @Author :ZH_JC
# @File :04_why_inheritance.py
# @Time :2025/2/15 16:19
# 使用继承的代码
# 编写父类Student
class Student:
name = None
age = None
__score = None
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print(f"name={self.name} age={self.age} score={self.__score}")
def set_score(self, score):
self.__score = score
# 子类只保存特有的方法:
# 小学生类: 继承Student
class Pupil(Student):
def testing(self):
print("...小学生在考小学数学...")
# 大学生类: 继承Student
class Graduate(Student):
def testing(self):
print("...大学生在考高等数学...")
# 测试
student1 = Pupil("apple", 10)
student1.testing()
student1.set_score(70)
student1.show_info()
print("------------------")
student2 = Graduate("grape", 22)
student2.testing()
student2.set_score(80)
student2.show_info()
- 小结:继承给编程带来的便利:
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
P121 继承注意事项和细节 2025/2/15
一、注意事项和细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
# @Author :ZH_JC
# @File :06_inheritance_detail.py
# @Time :2025/2/15 19:29
class Base:
# 公共属性
n1 = 100
# 私有属性
__n2 = 200
def __init__(self):
print("Base 构造方法....")
def hi(self):
print("hi() 公共方法")
def __hello(self):
print("__hello()私有方法")
# 提供公共方法,可以访问私有属性和方法
def test(self):
print("属性:",self.n1, self.__n2)
self.__hello()
class Sub(Base):
# 子类的构造器
def __init__(self):
print("Sub 构造方法....")
def say_ok(self):
#我们发现父类的非私有属性和方法可以访问
print("say_ok()",self.n1)
self.hi() # hi()公共方法
#我们发现父类的私有属性和方法不可以访问
# print(self.__n2) # 不会成功
# self.__hello() # 方法同样报错
# 创建子类对象
sub = Sub()
sub.say_ok()
# 调用子类继承父类的公共方法去实现访问父类的私有成员效果
sub.test() # 属性: 100 200 __hello()私有方法
- 图中可以看出子类是继承于父类的:
- Python编程语言中,
"object"
是所有其它类的基类
- Python支持多重继承
# 继承多个基类的定义语句:
class DerivedClassName(Base1,Base2,Base3...):
<statement-1>
<statement-N>
代码演示:
class A:
n1 = 100
def sing(self):
print("A sing()...",self.n1)
class B:
n2 = 200
def dance(self):
print("B dance()...",self.n2)
# C类继承了A和B类:
class C(A, B):
# Python pass 是空语句,是为了保持程序结构的完整性
# pass 不做任何事情,一般用做占位语句
pass
c = C()
print("----------------")
# 继承的属性信息
print(f"属性信息 {c.n1} {c.n2}")
# 调用继承的方法
c.dance()
c.sing()
- 同样可以看到C的n1来自于A,n2来自于B
- 在多重继承中,如果有同名的成员,遵守从左到右的继承优先权(即:写左边的父类优先级高,写在右边的父类优先级低)
class DerivedClassName(Base1,Base2,Base3...): # 优先base1
- 代码演示:
class A:
n1 = 100
class B:
n2 = 200
n1 = 300
class C(B, A): # 取决于A,B左右的排列
pass
c = C()
print("----------------")
print(f"属性信息 {c.n1} {c.n2}")
P122 继承课堂练习 2025/2/17
一、练习题
- 以下代码输出?
# @Author :ZH_JC
# @File :07_inheritance_exercise.py
# @Time :2025/2/17 11:20
class GrandPa:
name = "大头爷爷"
hobby = "旅游"
class Father(GrandPa):
name = "大头爸爸"
age = 39
class Son(Father):
name = "大头儿子"
son = Son()
# 如果在本类没有找到就在父类去找,父类没有就在父类的父类去找
print(f"son.name={son.name} son.age={son.age} son.hobby={son.hobby}")
- 编程题:
- 编写Computer类,包含CPU、内存、硬盘等属性
- get_details方法用于返回Computer的详细信息
- 编写PC子类,继承Computer类,添加特有属性【品牌brand】
- 编写NotePad子类,继承Computer类,添加特有属性【color】
- 完成测试,创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法打印输出信息。
# @Author :ZH_JC
# @File :08_inheritance_exercise08.py
# @Time :2025/2/17 11:30
# * 编写Computer类,包含CPU、内存、硬盘等属性
# 1) get_details方法用于返回Computer的详细信息
# 2) 编写PC子类,继承Computer类,添加特有属性【品牌brand】
# 3) 编写NotePad子类,继承Computer类,添加特有属性【color】
# 4) 完成测试,创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法打印输出信息。
"""
思路分析:
1. 父类:Computer
2. 公共属性:CPU(cpu), 内存(memory), 硬盘(disk)
3. 构造器(这里没有set设置属性,所以需要构造器):__init__(self, cpu, memory, disk)
4. 方法: get_details(self)
5. 子类:PC
6. 公共属性 brand
7. 构造器:__init__(self,cpu, memory, disk,brand) 父类继承的属性同样需要传入
8. 方法:print_info(self) 完成功能: 输出对象的信息(即属性的信息)
"""
class Computer:
cpu = None
memory = None
disk = None
def __init__(self,cpu,memory,disk):
self.cpu = cpu
self.memory = memory
self.disk = disk
def get_details(self):
return f"详细信息:cpu:{self.cpu} memory:{self.memory} disk:{self.disk}"
class PC(Computer):
brand = None
def __init__(self,cpu,memory,disk,brand):
# 1. 初始化子类的属性-方式1
self.cpu = cpu
self.memory = memory
self.disk = disk
self.brand = brand
# 2. 初始化子类的属性-方式2
# 在子类的构造器可以调用父类的构造器是可以的
"""
解读: super().__init__(cpu,memory,disk)
1. 通过super().xx的方式可以调用父类的方法
2. 这里我们就通过 super().__init__(cpu,memory,disk)去调用父类构造器
完成对父类属性的初始化任务(相当于把父类初始化的任务就交给父类的构造器去完成)
3. self.brand = brand 表示子类特有的属性,由子类构造器完成初始化
"""
super().__init__(cpu,memory,disk)
self.brand = brand
def print_info(self):
"""
完成当前子类的当前信息
"""
# 后面父类的属性可以通过在子类调用父类的方法进行输出
print(f"品牌:{self.brand} {self.get_details()}")
class Notepad(Computer):
pass
# 可以在这里下断点进行调试,查看详细过程
pc = PC("inter",32,1000,"戴尔")
pc.print_info()
P123 调用父类成员细节 2025/2/17
一、基本介绍
-
出现重名的时候,子类是不能使用self调用父类
-
如果子类和父类出现同名的成员,可以通过父类名、super()访问父类的成员
-
基本语法:
- 访问父类成员1
-访问成员变量:父类名.成员变量 -访问成员方法:父类名.成员方法(self)
- 访问父类成员2
访问成员变量:super().成员变量 访问成员方法:super().成员方法()
- 代码展示:
# @Author :ZH_JC
# @File :09_访问父类成员.py
# @Time :2025/2/17 19:22
# 通过父类名访问父类成员的形式
class A:
n1 = 100
def run(self):
print("A-run()....")
class B(A):
# n1 = 200
def run(self):
print("B-run()....")
# say方法: 通过父类名访问父类成员
def say(self):
# self.n1会现在本类找
print(f"父类的n1 {A.n1} 本类的n1 {self.n1}") # 100 200
# 调用父类的run
A.run(self)
# 调用本类的run
self.run()
# hi方法:通过super()方式去访问父类成员
def hi(self):
# 直接调用的是一级父类,直接父类
print(f"父类的n1 {super().n1}")
# 调用父类的run
super().run()
b = B()
# b.say()
b.hi()
二、注意事项和细节
- 子类不能直接访问父类的私有成员
# @Author :ZH_JC
# @File :10_访问父类成员细节.py
# @Time :2025/2/17 19:38
class A:
n1=100
__n2=600
def run(self):
print("A-run()....")
def __jump(self):
print("A-jump()....")
class B(A):
def say(self):
# # 子类不能直接访问父类的私有成员和方法
# print(A.__n2)
# print(super().__n2)
A.__jump(self)
super().__jump()
print("say()....")
b=B()
b.say()
- 访问不限于直接父类,而是建立从子类向上级父类的査找关系 A->B->C…
class Base:
n3=800
def fly(self):
print("Base-fly()...")
class A(Base):
n1=100
__n2= 600
def run(self):
print("A-run()....")
def jump(self):
print("A-jump()....")
class B(A):
def say(self):
print("say()...")
# 访问不限于直按父类,而是建立从子类向上级类的查找关系A->B->Base...
# Base.n3:表示直接访问Base类的n3属性--->800
# A.n3 : 表示直接访问A类的n3--->800
# suepr().n3: 表示从B类的直接父类去访问n3 -->800
print(Base.n3, A.n3, super().n3)
# 这里一定要带self,因为前面的对象是类名,而不是对象名
Base.fly(self) # 直接访问Base的fly
A.fly(self) # 直接访问A的fly
super().fly() # 直接调用当前类的直接父类A
self.fly() # 调用本类B的方法
b = B()
b.say()
- 建议使用
super()
的方式,因为如果使用父类名方式,一旦父类变化,类名需要统一修改,比较麻烦。
P124 调用父类成员练习题 2025/2/18
一、练习题
- 分析下面的代码,看看输出什么内容?
# @Author :ZH_JC
# @File :11_super_exercise.py
# @Time :2025/2/19 18:38
class A:
n1 = 300
n2 = 500
n3 = 600
def fly(self):
print("A-fly()...")
class B(A):
n1 = 200
n2 = 400
def fly(self):
print("B-fly()...")
class C(B):
n1 = 100
def fly(self):
print("C-fly()...")
def say(self):
print(self.n1) # 100
print(self.n2) # 400
print(self.n3) # 600
print(super().n1) # 200
print(B.n1) # 200
print(C.n1) # 100
c = C()
c.say()
- 针对上面的程序,想在C的say()中,调用C的fly()和 A的fly(),应该如何调用?
# 想在C的say()中,调用C的fly()和A的fly(),应该如何调用?
self.fly() # C-fly
A.fly(self) # A-fly
super().fly() # B-fly (如果B类中没有fly,则可以调用到A的fly)
P125 重写Override 2025/2/20
一、基本介绍
- 重写又称覆盖(override),即子类继承父类的属性和方法后,根据业务需要,再重新定义同名的属性或方法。
二、编程题
- 编程题:
- 编写一个Person类,包括属性(name、age),构造方法、say方法(返回Person自我介绍的字符串)
- 编写一个Student类,继承Person类,增加属性(id、score),以及构造方法,重写say方法(返回Student自我介绍的信息)
- 分别创建Person和Student对象,调用say方法输出自我介绍,体会重写的作用
# @Author :ZH_JC
# @File :12_override_exercise.py
# @Time :2025/2/20 12:16
class Person:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
return f"名字:{self.name} 年龄:{self.age}"
class Student(Person):
id = None
score = None
def __init__(self, name, age, id, score):
# 调用父类构造器
super().__init__(name, age)
# 子类属性自己完成
self.id = id
self.score = score
def say(self):
# suepr().say()调用父类的方法
return f"{super().say()} ID:{self.id} 成绩:{self.score}"
person = Person("张三", 18)
student = Student("李四", 22, 2, 90)
print(person.say())
print(student.say())
P126 类型注解介绍 2025/2/20
- 参考文档:https://docs.python.org/zh-cn/3.12/glossary.html#glossary
一、基本介绍
-
为什么需要类型注解:
-
随着项目越来越大,代码也就会越来越多,在这种情况下,如果没有类型注解,很容易不记得某一个方法的参数类型是什么。
-
一旦传入了错误类型的参数,python是解释性语言,只有运行时候才能发现问题,这对大型项目来说是一个巨大的灾难
-
代码案例:
#对字符串进行遍历
def fun1(a):
for ele in a:
print(ele)
# ctr+p 提示参数时,没有类型提示
# 如果类型传错了,就会出现异常,比如传的是整型
fun1(100)
二、类型注解作用和说明
- 自python3.5开始,引入了类型注解机制,作用和说明如下:
- 类型提示,防止运行时出现参数类型、返回值类型、变量类型不符合。
- 作为开发文档附加说明,方便使用者调用时传入和返回参数类型
- 加入后并不会影响程序的运行,不会报正式的错误,只有提醒
- PyCharm支持类型注解,参数类型错误会黄色提示
# a:str : 给形参a进行类型注解,标注形参a的类型是str
def fun1(a: str):
P127 类型注解使用 2025/2/20
一、类型注解
基本语法:
- 变量:类型
"""
解读:
1. n1:int : 对n1进行类型注解,标注n1的类型为int
2. 如果给出值的类型和标注的类型不一致,则pycharm会给出黄色警告
"""
n1:int= 10
n2: float = 10.1
is_pass: bool = True
name: str="燕赤霞"
- 实例对象类型注解:
class Cat:
pass
#实例对象类型注解
# cat: Cat :对cat进行类型注解,标注cat的类型时Cat
cat: Cat = Cat()
- 容器变量注解:
#容器类型注解
"""
解读:
1. my_list:对my_list进行类型注解,标注my_list的类型时list
2...
"""
my_list: list =[100, 200, 300]
my_tuple: tuple = ("run", "sing", "fly")
my_set: set = {"jack", "tim", "hsp"}
my_dict: dict = {"no1":"北京","no2":"上海"}
- 容器详细类型注解:
# 容器详细类型注解
"""
1. my_list2:list[int] 对my_list2进行类型注解:
标注my_list2类型是list,而且该list元素是int
"""
my_list2:list[int]=[100,200,300]
# 元组类型设置详细类型注解,需要把每个元素类型都标注一下
my_tuple2:tuple[str,str, str,float]=("run","sing","fly",1.1)
my_set2:set[str]= {"jack","tim","hsp"}
# 字典类型设置详细类型注解,需要设置两个类型,即[key类型,value类型
# my_dict2:dict[str,int] 标注my_dict2是字典类型,key的类型时str,值的类型时int
my_dict2:dict[str,int]={"no1":100,"no2":200}
- 在注释中使用注解:
# 注释中使用注解
# 解读 # type: float 用于标注 变量n3 的类型是 float
n3 = 89.0 # type: float
my_list3=[100,200,300] # type: list[int]
email = "hsp@sohu.com" # type: str
- 函数(方法)中的类型注解:
基本语法:
def 函数/方法名(形参名:类型,形参名:类型 ....)-> 返回值类型:
函数/方法体
案例:
"""
解读:
1. name:str 对形参name进行类型注解:标注name类型时str
2. 在调用方法/函数时,传入的实参类型不是一样的,则给出黄色的警告
"""
def fun1(name: str):
for ele in name:
print(ele)
fun1("zjc")
# 接收两个整数,返回整数
"""
分析:
1. a: int, b: int 对形参a和b进行类型注解,-> int对返回值进行类型注解
"""
def fun2(a: int, b: int) -> int:
return a + b
print(f"结果是:{fun2(10, 20)}")
二、说明
- 类型注解是提示性的,并不是强制性的,如果你给的类型和指定/标注的类型不一致,
PyCharm
检测到会给出黄色警告,但是仍然可以运行
三、union类型
- 参考文档:https://docs.python.org/zh-cn/3.12/library/typing.html#typing.Union
- 基本介绍:
- Union类型可以定义联合类型注解
- 在变量、函数(方法)都可以使用Union联合类型注解
- 使用的时候,需要先导入
Union:from typing import Union
- 基本语法:
Union[类型,类型…]
比如:联合类型:Union[X,Y,Z,.] 等价于 X|Y ,意味着满足 X 或Y之一
- 代码演示:
# @Author :ZH_JC
# @File :16_union_type_hint.py
# @Time :2025/2/20 15:34
# 如果要使用union联合注解,则需要导入union
from typing import Union
# 联合类型注解,a 可以是int或者str
a: Union[int, str] = 100
# my list是list类型,元素可以是int或者str
my_list: list[Union[int, str]] = [100, 200, 300, "tim"]
# 函数方法使用联合类型注解
# 接收两个数(可以是int/float),返回数(int/float)
def cal(num1: Union[int, float],
num2: Union[int, float]) -> Union[int, float]:
return num1 + num2
print(f"结果是: {cal(10, 20.2)}")
P128 多态问题引出 2025/2/21
一、问题引出
- 代码展示:
# @Author :ZH_JC
# @File :17_master_feed_animal.py
# @Time :2025/2/21 11:56
# 先使用传统的方式写
class Food:
name = None
def __init__(self,name):
self.name = name
class Fish(Food):
# 特有属性和方法
pass
class Bone(Food):
# 特有属性和方法
pass
class Animal:
name = None
def __init__(self,name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Master:
name = None
def __init__(self,name):
self.name = name
# 给猫猫喂鱼
def feed_cat(self, cat:Cat, fish:Fish):
print(f"主人:{self.name} 给动物:{cat.name} 喂的食物是:{fish.name}")
# 给狗狗喂骨头
def feed_dog(self, dog:Dog, bone:Bone):
print(f"主人:{self.name} 给动物:{dog.name} 喂的食物是:{bone.name}")
# 测试
master = Master("老韩")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
master.feed_cat(cat,fish)
master.feed_dog(dog,bone)
# 问题分析:如果动物/食物种类很多,怎么办?
- 问题分析:
- 问题分析:如果动物/食物种类很多,怎么办?
- 问题是:代码的复用性不高,而且不利于代码维护和功能扩展
- 解决方案:引出我们要讲解的多态
P129 多态继承和使用 2025/2/21
一、多态介绍
- 怎么理解多态:
- 多态顾名思义即 多种状态,不同的对象调用相同的方法,表现出不同的状态,称为多态。
- 多态通常作用在继承关系上(后面有特别说明)
- 解读:
- 举例说明:一个父类,具有多个子类,不同的子类对象调用相同的方法,执行的时候产生不同的状态,就是多态。
# @Author :ZH_JC
# @File :18_poly_examply.py
# @Time :2025/2/21 13:02
class Animal:
def cry(self):
pass
class Cat(Animal):
def cry(self):
print("小猫 喵喵叫...")
class Dog(Animal):
def cry(self):
print("小狗 汪汪叫...")
class Pig(Animal):
def cry(self):
print('小猪 噜噜叫...')
# 注意:1. 在python面向对象编程中,子类类型可以传递给父类类型;如果给的类没有相应的继承关系,则会有警告
def func(animal: Animal):
# 这里传入的类型其实是cat
print(f"animal 类型是{type(animal)}")
# 这里就调用相同的方法,但执行的时候会产生不同的状态
animal.cry() # 这里调用理应也是cat类,如果cat类没有,才会去父类查找
# 创建三个对象:
cat = Cat()
dog = Dog()
pig = Pig()
# 调用函数
func(cat)
func(dog)
func(pig)
- 多态的好处:
- 增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如
func(animal)
,代码说明 - 增加了程序的可扩展性,通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用
func(animal)
去调用
- 增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如
# 这里不需要改变父类的代码或者其他类,扩展性强
class Bird(Animal):
def cry(self):
print("小鸟 喳喳叫...")
- 特别说明python的多态特点:
- Python中函数/方法的参数是没有类型限制的,所以多态在python中的体现并不是很严谨(比如:和java等强类型语言比)
- Python并不要求严格的继承体系,关注的不是对象的类型本身,而是它是否具有要调用的方法(行为)
class AA:
def hi(self):
print("AA-hi()...")
class BB:
def hi(self):
print("BB-hi()...")
# 这里没有类型注解,不管你是什么类型,只关注是否有调动的方法
def test(obj):
obj.hi()
aa = AA()
bb = BB()
test(aa)
test(bb)
二、二说主人喂动物问题
- 代码优化:
class Master:
name = None
def __init__(self,name):
self.name = name
def feed(self, animal:Animal,food:Food):
print(f"主人:{self.name} 给动物:{animal.name} 喂的食物是:{food.name}")
# 测试
master = Master("老韩")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
master.feed(cat,fish)
master.feed(dog,bone)
- 可以扩展一下 Horse,Grass,体会多态的机制 带来的好处
# @Author :ZH_JC
# @File :17_master_feed_animal.py
# @Time :2025/2/21 11:56
from doctest import master
# 使用多态特性来优化:
class Food:
name = None
def __init__(self,name):
self.name = name
class Fish(Food):
# 特有属性和方法
pass
class Bone(Food):
# 特有属性和方法
pass
class Grass(Food):
pass
class Animal:
name = None
def __init__(self,name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Horse(Animal):
pass
class Master:
name = None
def __init__(self,name):
self.name = name
# 主人给动物喂食物:(多态)
def feed(self, animal:Animal,food:Food):
print(f"主人:{self.name} 给动物:{animal.name} 喂的食物是:{food.name}")
# 测试
master = Master("老韩")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
horse = Horse("枣红马")
grass = Grass("嫩草")
master.feed(cat,fish)
master.feed(dog,bone)
master.feed(horse,grass)