尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python中的super().__init__()用法详解
前言写子类时很多人会习惯性地在__init__里加一句super().__init__()问为什么答「网上都这么写」。这句到底在做什么、什么时候会出错、多重继承下为什么顺序和你以为的不一样往往说不清楚。先纠正一个流传极广的说法super()返回的不是「父类」。官方文档的措辞是——它返回一个代理对象把方法调用委托给某个类的父类或兄弟类具体委托给谁由object_or_type的方法解析顺序MRO, Method Resolution Order决定搜索从type之后的那一个类开始。也就是说super()找的是「MRO 里的下一个」而不是「我的直接父类」。单继承场景下这两者恰好重合所以这个误解一直没被发现直到遇上多重继承。还有一个必须点明的版本差异零参数的super()是 Python 3 才有的写法。Python 2 里必须写成super(ClassName, self).__init__()而且对老式类classic class根本不能用。Python 2.7 已于2020 年 1 月 1 日停止维护PEP 373所以本文一律用 Python 3 的零参写法看到super(C, self)的老代码知道它是 2 时代的产物即可。本文只讲super().__init__()这一件事为什么需要它、super()到底返回什么、零参形式凭什么能工作、多重继承下怎么协作以及忘了调用时会发生什么。一、为什么要在子类里调用父类的 __init____init__不是构造对象的唯一环节真正创建实例的是__new__但它是绑定实例属性的地方。子类如果定义了自己的__init__它就完全覆盖了父类的同名方法——Python 不会自动帮你把父类那份也跑一遍。# 适用于 Python 3.8class Animal:def __init__(self, name):self.name nameclass Dog(Animal):def __init__(self, name, breed):self.breed breed # 只绑定了自己的属性d Dog(旺财, 柴犬)print(d.breed) # 柴犬print(d.name) # AttributeError: Dog object has no attribute nameDog的__init__一写Animal.__init__就再也不会自动执行self.name从来没被赋过值。这就是「忘了调super().__init__()」最直接的后果属性缺失而且报错发生在离出错点很远的地方——你在d.name那一行才看到AttributeError很难联想到是构造函数没串起来。补上之后就正常了# 适用于 Python 3.8class Dog(Animal):def __init__(self, name, breed):super().__init__(name) # 先让父类把 name 绑好self.breed breed顺序上建议先调用父类初始化再设置子类自己的属性。反过来的话如果父类的__init__也要写同一个属性名你子类的赋值会被父类覆盖掉症状同样隐蔽。二、super() 的真正含义MRO 里的「下一个」super()的官方签名是class super(type, object_or_typeNone, /)文档给了一个非常清晰的例子如果object_or_type的__mro__是D - B - C - A - object而type是B那么super()搜索的范围就是C - A - object。它跳过的是B自己从B的下一个开始找而不是从「B 的父类」找。MRO 可以用类.__mro__或等价的类.mro()查看# 适用于 Python 3.8class A:passclass B(A):passclass C(A):passclass D(B, C):passfor cls in D.__mro__:print(cls.__name__)打印顺序是D、B、C、A、object。Python 用的是C3 线性化算法官方文档说明它「即使在钻石型继承结构下也能正确工作」。这个顺序不是凭感觉排的它保证了两件事每个类只出现一次且子类永远排在父类之前。理解这一点后super().__init__()的准确含义就是在这条 MRO 链上从当前类往后的第一个定义了__init__的类。单继承时它是父类多重继承时它可能是「兄弟类」。三、零参 super() 为什么能在方法里工作这是最让人困惑的一点super()明明是零参数它怎么知道当前类是谁、当前实例是谁答案是编译器的隐式闭包。官方文档语言参考的「创建类对象」一节写道__class__是编译器在类体里创建的一个隐式闭包引用——只要类体中任何方法引用了__class__或super编译器就会创建它。这样零参super()就能通过词法作用域确定「正在定义的是哪个类」而「当前实例」则取自紧邻外层函数的第一个参数通常叫self。由此推出几个直接结论零参super()只能在类体内的普通方法里用。写在模块顶层、或者写在一个不属于任何类的函数里都会报RuntimeError: super(): no arguments。它在嵌套函数里不能如预期工作包括生成器表达式——因为那些东西会隐式创建一层新的函数作用域第一参数不再是self。官方文档对此有明确提示。需要跨作用域时就用两参形式把话说死super(Dog, self).__init__(name)。两参形式不限于方法内使用在任何地方都能用只要你把类与实例写清楚。# 适用于 Python 3.8class A:def __init__(self, x):self.x xclass B(A):def __init__(self, x):# 两参形式明确指定「从 B 之后开始找」super(B, self).__init__(x)# 上面这行与零参的 super().__init__(x) 等价在 3 里既然等价为什么还推荐零参因为类名改了不用同步改代码减少了漏改的风险——这正是零参形式被设计出来的动机。四、协作式多重继承菱形结构怎么走多重继承里每个__init__都调用super().__init__()整条 MRO 链就会被完整地、每个类恰好一次地走一遍。这种写法叫协作式cooperative继承。它要求每个类都用同样的调用约定把不认识的参数原样往上传。# 适用于 Python 3.8class Base:def __init__(self, **kwargs):print(Base.__init__, kwargs)super().__init__() # 这里之后就是 object链路到此结束class Logger(Base):def __init__(self, *, verboseFalse, **kwargs):super().__init__(**kwargs)self.verbose verboseclass Storage(Base):def __init__(self, *, path, **kwargs):super().__init__(**kwargs)self.path pathclass App(Logger, Storage):def __init__(self, name, **kwargs):super().__init__(**kwargs)self.name nameapp App(demo, verboseTrue, path/tmp/data)print(app.name, app.verbose, app.path)App.__mro__是App - Logger - Storage - Base - object。App.__init__调super().__init__(**kwargs)进到LoggerLogger处理后把剩下的**kwargs继续传给super().__init__进到StorageStorage再传到BaseBase最后调super().__init__()时指向objectobject.__init__()不接受额外参数但这时kwargs已经是空的所以平安通过。这里有三条硬约束每个类都要调super().__init__()链一断后面的类就全被跳过。每个类都要把多余的参数用**kwargs传下去否则下游类拿不到它需要的参数。keyword-only 参数*后面那些是这套约定的好帮手它让「谁消费哪个参数」一目了然也避免了位置参数顺序被打乱。对比一下「不协作」的写法问题就很明显# 适用于 Python 3.8class App2(Logger, Storage):def __init__(self, name, verbose, path):Logger.__init__(self, verboseverbose) # 显式点名Storage.__init__(self, pathpath) # 也显式点名self.name name这种写法在简单场景下能用但它绕开了 MRO一旦Logger和Storage有共同祖先那个祖先的__init__会被执行两次。菱形继承里这是典型 bug——初始化做了两遍注册回调重复、连接被建了两次、计数器翻倍而报错信息通常完全指不到这里。五、object.__init__ 的边界链路最后一定会走到object。官方文档对object的描述是它的构造函数不接受任何参数。所以在协作式链里Base那一层必须先把自己认识的参数全部消费掉才能用空参数调用super().__init__()。反过来如果你在调用时把多余的参数一路传到object就会得到TypeError# 适用于 Python 3.8class Bad:def __init__(self, **kwargs):super().__init__(**kwargs) # kwargs 非空时object 会拒绝class Sub(Bad):def __init__(self):super().__init__(unknown1) # TypeError: object.__init__() takes exactly one argument# Sub() # 取消注释就会看到上面那个 TypeError这个报错的措辞乍看莫名其妙明明是子类在传参但知道它是object发出来的就明白问题出在「没人消费unknown这个参数」。另外super()在 Python 3.14 起还支持被pickle和copy这是历史遗留的限制被解除日常几乎用不到知道即可。常见坑点1. 子类写了__init__却忘了调super().__init__()。❌class Dog(Animal): def __init__(self, breed): self.breed breed—— 父类属性全丢之后某处d.name抛AttributeError。 ✅ 第一句写super().__init__(name)把父类的初始化接上。2. 以为super()就是「我的父类」。❌ 多重继承里指望super()一定指向直接父类。 ✅ 它指向MRO 里当前类的下一个用类.__mro__看真实顺序别猜。3. 在嵌套函数或生成器表达式里用零参super()。❌ 在方法内部定义一个函数里面写super().__init__()—— 外层函数的第一个参数不再被当作self行为不符合预期。 ✅ 要么把super()调用挪回方法体内要么改成两参形式super(CurrentClass, self)。4. 在模块顶层或类外的函数里用零参super()。❌super().__init__()写在类外面 ——RuntimeError。 ✅ 零参super()只适用于类体内的普通方法其他位置用两参形式。5. 多重继承里显式点名调用父类方法。❌Logger.__init__(self, ...)与Storage.__init__(self, ...)并列 —— 共同祖先被初始化两次。 ✅ 统一用super().__init__(**kwargs)让 MRO 决定顺序保证每个类只跑一次。6. 协作链里吞掉了**kwargs。❌def __init__(self, *, path, **kwargs): super().__init__()—— 直接把**kwargs丢了下游类拿不到自己的参数。 ✅super().__init__(**kwargs)把不认识的原样传下去。7. 把参数一路传到object。❌ 最底层没有消费掉剩余参数就super().__init__(**kwargs)——TypeError: object.__init__() takes exactly one argument。 ✅ 链的末端用空参数调用super().__init__()确认没有参数剩下。8. 照抄 Python 2 的super(C, self)却没注意类名。❌ 类重命名后老代码里super(OldName, self).__init__()还在 —— 直接NameError或者行为错乱。 ✅ Python 3 用零参super()super(C, self)那种写法是 Python 2 时代必需的2.7 已于 2020-01-01 停止维护新代码不必再写。总结疑问答案super()返回什么一个代理对象按 MRO 找类找的范围从当前类在 MRO 中的下一个开始零参形式凭什么能工作编译器为类体创建隐式__class__闭包引用零参形式的限制只能在类体内的普通方法里用嵌套函数里不按预期工作单继承要注意什么子类写了__init__就必须调用父类的多重继承要注意什么每层都调super().__init__(**kwargs)并透传参数为什么不能点名调用父类菱形结构下会导致共同祖先被初始化多次链路终点object.__init__()不接受参数末端必须空参调用Python 2 对照super(C, self).__init__()是 2 时代的必需写法super().__init__()这一行代码背后的模型其实只有一条主线Python 的继承查找是一条线性化的 MRO 链super()就是「沿链往下走一格」。单继承时它看起来像「调用父类」多重继承时它变成「调用下一个协作伙伴」。把「父类」这个词从脑子里换掉菱形继承下的种种怪现象就都能解释了。
RELATED

相关推荐

技术周榜深度拆解:开发工具、AI与前端项目的选型逻辑与避坑指南

技术周榜深度拆解:开发工具、AI与前端项目的选型逻辑与避坑指南

1. 周榜数据背后的信号:为什么值得花时间逐项拆解每周刷一次热榜的人很多,但真正把榜单当成"技术风向标"来读的人很少。大多数人扫一眼项目名,看到几个眼熟的词,点进去瞄两眼 README,然后关掉页面&#xff0…

📅 2026/10/9 20:43:21
Codex 插件实战:12 个提升 AI 编程效率的必备工具

Codex 插件实战:12 个提升 AI 编程效率的必备工具

用 Codex 写代码这件事,我最深的体会是:它本身确实聪明,但真正决定效率上限的,往往不是它生成代码的能力,而是它对你的项目了解多少。我接手过一个老项目,目录结构乱、依赖多、历史包袱重,每次让…

📅 2026/10/9 20:43:21
机器视觉镜头选型指南:从焦距计算到现场验收的工程实践

机器视觉镜头选型指南:从焦距计算到现场验收的工程实践

简介:这份PPT学习教案面向机器视觉入门者、自动化检测工程师及相关专业师生,系统讲解镜头这一视觉系统核心部件。内容从图像质量参数切入,剖析分辨率、对比度、景深与畸变对成像的影响,并延伸至光心、主轴、焦点、焦平面、共轭关系…

📅 2026/10/9 20:43:21
MORE NEWS

更多资讯

📰

多智能体交通信号控制仿真实战:从路口建模到Q-learning协调优化

简介:一份基于多智能体算法的城市交通信号控制仿真系统源码包,面向交通工程、人工智能及智能交通系统的研究者与开发者,用于构建虚拟交通环境、模拟不同车流状况,并验证信号灯智能体之间的协同控制策略。压缩包共173个文件、约49.…

📰

Xilinx 7系列FPGA收发器选型指南:GTX/GTH/GTP/GTZ全解析

搞过Xilinx 7系列FPGA的人,几乎没有不在GTX、GTH、GTP、GTZ这四个缩写前懵过的。无论是想做PCIe Gen3、万兆以太网还是JESD204B高速采集,第一件事就是搞清楚板上这颗FPGA到底带不带收发器、带哪种,结果一看数据手册,四种缩写长得差…

📰

AI 时代开发者如何保持竞争力:你需要强化的三项核心技能

AI 正在改变开发者的工作方式。手写代码依然必要,但你现在更需要懂得如何指挥 AI、评估质量、权衡技术方案并做出决策。这其实不难,你可以从以下三个方向开始练手。 变成调度者:像项目经理一样指挥 AI 以前开发新功能,你得亲自搞定…

📰

AI 怎么挖出 24 个安卓漏洞?拆解 Taskflow 审计逻辑与它的真实盲区

把整个项目的代码一股脑丢给大模型,让它“帮忙找找漏洞”,可能只换来泛泛而谈的判断和无法触发的假报警。但如果换一种思路,把审计做成工序严密的流水线,情况就会截然不同。GitHub Security Lab 团队利用开源的 Taskflow Agent&am…

📰

基于OpenCV与深度学习的车牌识别系统开发实战

简介:这是一份基于Python与OpenCV,并结合深度学习模型完成的车牌识别毕业设计完整资料包,适合正在准备毕业设计、课程设计或期末大作业的本科学生,也适合想通过实际项目入门图像处理与CNN应用的新手。压缩包共18个文件&#xff0c…

📰

月 400 刀到不到 20 刀:我是怎么把 OpenClaw 的 Token 账单砍掉 95% 的

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬