目录
上一章我们掌握了 @classmethod 类方法,学会统一操作全类共享的数据。但在写代码的过程里,经常会出现多个模板重复写相同属性、相同行为的情况,比如小猫、小狗都属于动物,都拥有名字、毛色,也都会吃饭睡觉,如果分开写两套完整代码,会出现大量重复内容。
本章我们学习继承机制,复用已写好的模板代码,还能自定义差异化逻辑;最后理解多态,一套调用逻辑适配多种对象,大幅精简重复代码
一、通俗理解继承
我们用动物举例更好理解:
- 父类 Animal:通用动物模板,存放所有动物通用特征(名字、毛色)和基础行为(吃饭、睡觉)。
- 子类 Dog:小狗专属模板,不用重复写通用内容,直接沿用父类全部逻辑,只新增小狗独有的奔跑动作,还能改写吃饭的专属行为。
简单类比:父类是通用基础图纸,子类不需要从零绘制,直接复用图纸,再额外添加专属功能。
二、自动复用父类属性、方法
# 父类:通用动物模板
class Animal(object):
def __init__(self, name, colour):
self.name = name
self.colour = colour
def sleep(self):
print(f"{self.colour}的{self.name}正在睡觉")
子类 Dog,括号写入父类名称,代表继承 Animal
class Dog(Animal):
# 子类新增独有的行为
def run(self):
print(f"{self.name}撒欢向前奔跑")
创建小狗实例
dog = Dog("大黄", "白色")
调用从父类继承的 sleep 方法
dog.sleep()
调用子类独有的 run 方法
dog.run()
运行输出:
白色的大黄正在睡觉
大黄撒欢向前奔跑
可以看到子类没有定义构造方法和 sleep,却能直接使用父类提前写好的全部逻辑。
三、方法重写:子类自定义专属行为
通用动物的吃饭逻辑比较笼统,小狗进食有专属行为,我们在子类定义和父类同名方法,就会覆盖原有逻辑,这个操作叫做方法重写。
class Animal(object):
def __init__(self, name, colour):
self.name = name
self.colour = colour
def eat(self):
print("动物正在吃饭")
def sleep(self):
print(f"{self.colour}的{self.name}正在睡觉")
class Dog(Animal):
# 重写父类 eat 方法,替换专属逻辑
def eat(self):
print(f"{self.name}正在大口吃狗粮")
def run(self):
print(f"{self.name}撒欢向前奔跑")
dog = Dog("大黄", "白色")
dog.eat()
dog.sleep()
dog.run()
运行结果:
大黄正在大口吃狗粮
白色的大黄正在睡觉
大黄撒欢向前奔跑
规则:子类和父类方法名称完全相同时,调用会优先执行子类内部代码。这里要区分清楚:Python 不支持其他语言的方法重载(同名不同参数),我们讲的是方法重写(override)——子类定义和父类完全同名的方法,覆盖父类逻辑。
四、super() :子类重写构造方法
前面例子中,子类没有写自己的 __init__,直接复用父类构造。但如果子类自己写了 __init__,默认不会自动调用父类的 __init__,会拿不到父类属性,这是新手第一大坑。
解决办法:在子类 __init__ 中调用 super(),把父类构造方法也执行一遍,同时还能新增子类独有的属性。
class Animal(object):
def __init__(self, name, colour):
self.name = name
self.colour = colour
def sleep(self):
print(f"{self.colour}的{self.name}正在睡觉")
class Dog(Animal):
def __init__(self, name, colour, dog_type):
# 调用父类的构造方法,拿到 name、colour
super().__init__(name, colour)
# 子类新增自己独有的属性
self.dog_type = dog_type
def run(self):
print(f"{self.name}撒欢向前奔跑")
dog = Dog("大黄", "白色", "金毛")
dog.sleep()
dog.run()
print(dog.dog_type)
运行输出:
白色的大黄正在睡觉
大黄撒欢向前奔跑
金毛
一句话说明:当子类自己定义 __init__ 之后,不会自动继承父类构造,需要 super(),才能拿到父类定义的 name、colour。
为什么必须用 super()?子类一旦自己写了 __init__,就会完全覆盖父类的构造逻辑,父类里定义的 name、colour 都不会自动生成。super() 的作用就是先执行一遍父类的 __init__,把公共属性准备好,再继续添加子类独有的属性。如果不调用 super(),直接访问 self.name 会直接报错 AttributeError。
super() 的通用写法:super().__init__(父类需要的参数),把父类构造方法需要的参数原样传进去即可。子类新增的属性放在 super() 调用之后定义,逻辑更清晰。
极简对照示范:下面把错误写法和正确写法放在一起,一眼就能看懂差别。
错误写法(忘记调用 super())
class Dog(Animal):
def __init__(self, name, colour, dog_type):
# 没有调用 super().__init__,父类属性不会生成
self.dog_type = dog_type
dog = Dog("大黄", "白色", "金毛")
print(dog.name) # 报错:AttributeError
正确写法(调用 super())
class Dog(Animal):
def __init__(self, name, colour, dog_type):
# 先调用父类构造,拿到 name、colour
super().__init__(name, colour)
# 再新增子类独有属性
self.dog_type = dog_type
dog = Dog("大黄", "白色", "金毛")
print(dog.name) # 大黄
print(dog.dog_type) # 金毛
一句话总结:子类自己写 __init__ 后,第一行先写 super().__init__(父类需要的参数),父类属性就都能拿到了。
五、继承基础要点
- 继承写法:class 子类名(父类名),括号内填写需要复用的模板名称;
- 子类自动拥有父类所有实例属性、通用方法;
- 子类可以新增独有的方法,拓展专属功能;
- 同名方法会发生重写,子类逻辑优先级更高;
- Python 支持多层继承,子类可以继承另一子类的全部内容。
- 单继承:class Dog(Animal),只继承一个父类,最常用;
- 多继承:class Dog(Animal, RunAbility),同时继承多个父类,实际开发中优先使用单继承,多继承少用。
六、多态:一套逻辑适配不同对象
日常场景:人可以和不同小狗玩耍,不管小狗性格活泼还是安静,都不用单独写一套互动代码,只要小狗拥有相同的玩耍方法,就能统一调用,这就是多态的核心思路。
完整演示代码:
# 第一种小狗类型
class Dog1(object):
def __init__(self, name):
self.name = name
def play(self):
print(f"{self.name}疯跑打闹玩耍")
第二种小狗类型
class Dog2(object):
def __init__(self, name):
self.name = name
def play(self):
print(f"{self.name}安静慢慢玩")
人类模板,统一和小狗互动
class Person(object):
def __init__(self, name):
self.name = name
def play_with_dog(self, dog):
print(f"{self.name}和{dog.name}一起玩耍:")
dog.play()
实例化不同小狗、人物
dog1 = Dog1("小黄")
dog2 = Dog2("小黑")
person = Person("小王")
同一套方法,传入不同对象自动执行对应逻辑
person.play_with_dog(dog1)
print("--------------------")
person.play_with_dog(dog2)
输出效果:
小王和小黄一起玩耍:
小黄疯跑打闹玩耍
--------------------
小王和小黑一起玩耍:
小黑安静慢慢玩
多态核心特点:
- 多个不同类定义名称一致的方法;
- 同一个函数可以接收任意这类对象,不需要区分对象具体类型;
- 传入不同实例,会自动执行各自内部的逻辑,代码拓展性更强。
七、综合示例:猫狗双子类对比
我们新增小猫子类,直观对比继承与重写的使用效果:
class Animal(object):
def __init__(self, name, colour):
self.name = name
self.colour = colour
def eat(self):
print("普通动物进食")
def sleep(self):
print(f"{self.colour}的{self.name}睡觉")
小狗子类
class Dog(Animal):
def eat(self):
print(f"{self.name}啃狗粮")
def run(self):
print(f"{self.name}快速奔跑")
小猫子类
class Cat(Animal):
def eat(self):
print(f"{self.name}舔猫粮")
def climb(self):
print(f"{self.name}爬上屋顶")
dog = Dog("大黄", "白色")
cat = Cat("小白", "橘色")
dog.eat()
dog.sleep()
dog.run()
print("================")
cat.eat()
cat.sleep()
cat.climb()
八、继承与多态适用场景
继承适用场景:多个类存在大量重复属性、通用行为,把公共内容抽取为父类,子类直接复用,减少重复书写,后续统一修改逻辑也只需要改动一处。
多态适用场景:需要一套统一调用逻辑兼容多种对象,只要求对象拥有同名方法,不用单独区分对象类型,提升代码通用性。
为了更直观地对比继承与多态的异同,下面用一张表格梳理两者的核心区别:
| 对比维度 | 继承 | 多态 |
|---|---|---|
| 核心目的 | 复用父类已有的属性与方法,减少重复代码 | 用一套统一调用逻辑适配多种对象,提升代码通用性 |
| 代码关系 | 子类与父类存在明确的继承关系,通过 class 子类(父类) 建立 | 不强制要求父子继承关系,只要对象拥有同名方法即可 |
| 使用场景 | 多个类存在大量重复属性、通用行为时,抽取公共内容为父类 | 需要一套调用逻辑兼容多种对象,且对象都具备同名方法时 |
| 实现方式 | 子类直接继承父类,可新增专属方法,也可重写同名方法 | 多个类定义名称一致的方法,由调用方统一传入不同实例 |
| 优点 | 减少重复书写,后续统一修改逻辑只需改动父类一处 | 调用方无需区分对象具体类型,代码拓展性更强 |
九、注意事项
- 重写仅针对方法,不会改动父类原始代码;
- 继承后可以直接使用父类全部属性,不用重复定义;
- 多态不强制要求父子继承关系,只要方法名称一致就能实现;
- Python 的多态属于鸭子类型:“长得像鸭子、叫得像鸭子,就当做鸭子”,不需要强制继承同一个父类,只要方法名相同就可以统一调用;
- 子类可以自由新增专属属性、独有方法,不会影响父类。
常见错误与排查
在使用继承和 super() 时,最容易踩到下面几个坑,提前了解能少走很多弯路。
- 忘记调用 super().__init__ 导致属性丢失:子类自己写了 __init__ 却没有调用 super().__init__,父类里定义的 name、colour 等属性就不会生成,访问 self.name 会直接报 AttributeError。解决办法:在子类 __init__ 第一行调用 super().__init__(父类需要的参数),把公共属性准备好。
- 多继承时的 MRO 问题:多继承时方法查找顺序由 MRO(方法解析顺序)决定,如果多个父类有同名方法,调用顺序可能和预期不一致,甚至出现菱形继承的复杂情况。解决办法:优先使用单继承;确需多继承时,用 类名.__mro__ 查看解析顺序,并让父类也正确调用 super() 保证协作。
- super() 参数传错或漏传:调用 super().__init__ 时没有把父类构造方法需要的参数原样传进去,会导致父类初始化失败或属性缺失。解决办法:仔细核对父类 __init__ 的参数列表,把需要的参数完整传入,子类新增属性放在 super() 调用之后定义。
十、本章小结
通过 class 子类(父类) 实现继承,子类复用父类全部属性与方法;子类可以新增专属方法,定义同名方法即可重写原有逻辑;多态依靠同名方法实现,同一套代码可适配多种对象;继承解决代码重复问题,多态提升代码兼容拓展能力。

1452

被折叠的 条评论
为什么被折叠?



