【从0-1学习Python | 第九篇】继承

前言

造一辆燃油车,再写一辆电动车。如果两个类各写一遍,品牌、型号、颜色的赋值要重复,启动、行驶、停止三段 print 也要重复。哪天想给所有车统一加个鸣笛,还得挨个类改。重复的代码越多,漏改的地方就越多。

继承处理的就是这类重复。父类写共性,子类在类名后面的括号里写上父类,父类的属性和方法就自动带过来了。class FuelCar(Car) 里,Car 写在括号里,FuelCar 继承 Car。

车与车之间不同的部分才需要落在子类:燃油车补充燃料,电动车充电。这份代码就是按这个思路拆的。

父类写共性

python 复制代码
class Car:
    def __init__(self, brand, model, color, owner):
        self.brand = brand  # 品牌(公有属性)
        self.model = model  # 型号(公有属性)
        self.color = color  # 颜色(公有属性)

        self.__owner = owner  # 拥有者(私有属性)

    def start(self):  # 启动
        print(f'{self.brand} {self.model} 正在启动...')

    def run(self):  # 行驶
        print(f'{self.__owner}:{self.brand} {self.model} 正在行驶...')

    def stop(self):  # 停止
        print(f'{self.brand} {self.model} 停止行驶...')

    def __control_fuel(self):  # 私有方法
        print(f'{self.brand} {self.model} 正在控制油门...')

    def get_owner(self):
        return self.__owner

    def charge(self):
        print(f'{self.brand} {self.model} 正在补充燃料...')

Car 的构造方法收四个参数:品牌、型号、颜色、拥有者。前三者存成公有属性,拥有者存成私有属性 __owner。start、run、stop 描述共有动作,charge 表示补充燃料,__control_fuel 是私有方法,get_owner 是读取拥有者的唯一入口。

这些成员都放在 Car 里,是因为所有车都有:不管烧油还是用电,都要启动、行驶、停止,也都有品牌和型号。共性写在这里,子类一行都不用重复。

子类括号里写父类

python 复制代码
class FuelCar(Car):
    def charge(self):
        super().charge()
        print(f'{self.brand} {self.model} 正在充电...')


class ElectricCar(Car):
    pass

FuelCar 的括号里写了 Car,继承声明就此完成,它只重写了 charge,先调父类实现,再打印自己的充电动作。ElectricCar 括号里同样写 Car,方法体是 pass,不写任何方法也仍然是合法子类,Car 的成员全都能用。

用 FuelCar 建一个实例并调用 charge:

python 复制代码
c1 = FuelCar("BMW", "X5", "黑色", "张三")
c1.charge()

实际输出是两行:

text 复制代码
BMW X5 正在补充燃料...
BMW X5 正在充电...

第一行由 Car 的 charge 打印,第二行由 FuelCar 的 charge 打印。两个类都有 charge,最后执行的是子类那一份,这就是方法重写。

子类实例上的属性查找

FuelCar 没有定义构造方法,实例化时却能照样传四个参数,值也全部挂在 c1 上。属性查找沿着继承链向上进行,FuelCar 里找不到,就用 Car 的构造方法完成初始化。

方法遵循同一套规则。c1.start() 可以调用,是因为 Car 里定义了 start;子类实例执行父类方法时,self 仍然是 c1 这个 FuelCar 对象,所以方法体里的 self.brand 取到的是 c1 自己的数据。run 里那句 self.__owner 同理,属性由 Car 的构造方法写进 c1,FuelCar 只是继承了读取它的方法。

继承拿到的是成员的定义和查找路径,实例数据始终属于子类对象本身。

方法重写

重写指子类定义与父类同名的方法,通过子类实例调用时执行子类的版本。FuelCar 的 charge 覆盖了 Car 的 charge,c1.charge() 走的是 FuelCar 那一份。

输出里的"正在补充燃料"来自 FuelCar 内部主动调用的 super().charge()。删掉这一行,结果就只剩"正在充电"。

决定执行哪个版本的是实例的实际类型,跟变量名怎么写无关。把 c1 换成 Car("BMW", "X5", "黑色", "张三"),charge 只会打印补充燃料,因为对象本身就是 Car,没有子类版本可以找。

ElectricCar 的 pass 说明子类可以不写任何方法,继承照样完整生效,构造方法、start、run、stop、charge 全部可用。等充电这件事需要独立语义时,重写才有价值。

super() 与父类名调用

调用父类方法有两种写法:

python 复制代码
super().charge()
Car.charge(self)

第二种是早期 Python 的写法,必须显式传入 self,否则调用时缺少实例参数。super() 不传参数时会按当前类和实例自动定位父类,等价于 super(FuelCar, self)。

差别在多继承场景下才会显现。super() 沿着 MRO(方法解析顺序)继续往下查找,而 Car.charge(self) 是写死跳到 Car,会跳过 MRO 中排在 Car 之前的类。单继承里两者结果一致,日常写作 super() 更稳妥。

构造方法也有同样的要求。子类自己定义了构造方法时,要调用 super().init(...) 把参数交回父类,否则父类的属性不会被初始化,后续访问会抛 AttributeError。

私有属性在子类里为什么碰不到

__owner 以双下划线开头,Python 会对这类名字做名称改写,在 Car 中实际存成 _Car__owner。子类方法里写 self.__owner,会被改写成 _FuelCar__owner,读到的是一个从未赋过值的属性,直接抛 AttributeError。

get_owner 因此成了必需品。c1.get_owner() 返回"张三",方法定义在 Car 里,执行的是父类代码,读到的正是构造方法当初写进去的那份数据。

双下划线带来的是语言层面的隔离,单下划线只是一种约定。_owner 这类名字只提示"这是内部属性",Python 不会改写它,子类可以直接访问。私有方法 __control_fuel 的处理方式与私有属性一致,子类里同样无法通过 self.__control_fuel() 调到它。

总结

继承把公共的属性和方法放在父类,子类在括号里声明父类后全部拿到;改行为用重写;复用父类逻辑用 super() 往下走;双下划线成员在子类里不可见,只能由父类提供读取入口。

看一段继承写得对不对,就看这四件事能否在代码里对上:子类实例身上有没有父类的属性、同名方法执行的是谁、super() 有没有漏、私有成员有没有被跨类直接访问。

相关推荐
青少儿编程课堂2 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:位运算实战——二进制权限与最大异或解析
c++·python·算法·bfs·信息学竞赛
2601_954005622 小时前
11 种触发 × 18 种行为:ricon 交互编排系统全解
前端·后端·物联网·数学建模·信息可视化·交互
在世修行2 小时前
智学文枢:理科分析引擎
python·ai·pyside·理科教学
青少儿编程课堂2 小时前
2026蓝桥杯青少组图形化真题:校园飞机大战解析(全国青少年信息素养·电子学会图形化考级)
c++·python·算法·bfs·信息学竞赛
wdfk_prog2 小时前
lwip教程 38:从 `lwipopts.h` 到 Port Contract——lwIP 裁剪、构建选择、OS Port 与 Network Port
java·开发语言
开开心心就好2 小时前
证件照排版打印工具,免费可自定义尺寸
服务器·开发语言·算法·随机森林·pdf·音视频·散列表
方知我2 小时前
01FastAPI基础入门
人工智能·python
我是高手高手高高手2 小时前
宝塔 PHP多版本,切换PHP默认版本
开发语言·php
CodeYp20192 小时前
第16讲:set 和 map 的使用
开发语言·c++·set·map