python深入讲解魔术方法
前言魔术方法magic methods就是那些名字两头带双下划线、由 Python 语法隐式调用的特殊方法比如__init__、__repr__、__eq__。它们不是给你手动调的接口而是给语言本身看的钩子你写了__len__len(obj)才有意义你写了__enter__/__exit__对象才能用在with里。语言参考把它叫作「特殊方法名」special method names这是 Python 实现运算符重载的正规途径。这里先纠正两个常见误解。第一很多人以为特殊方法写在实例上就能生效——不能。语言参考明确说明对自定义类而言隐式调用特殊方法只保证在对象的类型上查找不查实例字典。第二很多人以为__str__和__repr__是同义词可以随便挑一个——它们面向的读者不同repr面向开发者、str面向用户容器打印时用的是repr。本文按「对象生命周期 → 表示与比较 → 可调用与容器 → 上下文管理 → 属性访问」的顺序把魔术方法串一遍重点是那些机制层面的细节而不是方法清单。一、对象是怎么造出来的__new__ 与 __init__创建实例时Python 先调__new__造对象再调__init__初始化它。语言参考里的关键约定有三条__new__是一个静态方法被特殊处理你不需要显式加staticmethod第一个参数是cls如果__new__返回的不是cls的实例__init__不会被调用__init__不能返回非None的值否则运行期报TypeError。# 适用于 Python 3.8class Point:__slots__ (x, y)def __new__(cls, x, y):# 常规做法先让父类把对象造出来instance super().__new__(cls)return instancedef __init__(self, x, y):self.x xself.y y# 这里不要 return 任何东西返回非 None 会抛 TypeErrorp Point(1, 2)print(p.x, p.y)__new__主要用于不可变类型的子类int、str、tuple 等它们的值在对象创建时就定下来了等不到__init__要做校验或变换必须在__new__里改。二、表示__repr__ 与 __str__ 的分工语言参考说得很清楚__repr__由repr()调用应尽量返回一个「能重建该对象的合法 Python 表达式」__str__由str()、print()调用返回「好读」的表示即可。还有一条关键规则如果一个类定义了__repr__却没定义__str__那么str()会退回使用__repr__。反过来不成立——只定义__str__不会让repr()变好看。# 适用于 Python 3.8class Money:def __init__(self, amount, currencyCNY):self.amount amountself.currency currencydef __repr__(self):# 面向开发者信息丰富、无歧义理想情况能重建对象return fMoney(amount{self.amount!r}, currency{self.currency!r})def __str__(self):# 面向用户简洁return f{self.amount} {self.currency}print(str(Money(9.9))) # 9.9 CNYprint(repr(Money(9.9))) # Money(amount9.9, currencyCNY)print([Money(1), Money(2)]) # 列表里显示的是 repr不是 str最后一行是最容易被忽视的细节容器打印用的是元素的repr不会调__str__。所以只写好看的__str__而把__repr__留成默认调试时看到的就是一串难懂的内存地址。三、比较与哈希__eq__ 与 __hash__ 的联动这是魔术方法里最阴的一个联动机制。语言参考原文的意思是一个类如果覆写了__eq__却没有定义__hash__它的__hash__会被隐式置为None。一旦__hash__是None实例就不可哈希——不能放进set不能当dict的键hash()会抛TypeError并且会被isinstance(obj, collections.abc.Hashable)正确识别为不可哈希。# 适用于 Python 3.8class BadKey:def __init__(self, name):self.name namedef __eq__(self, other):return isinstance(other, BadKey) and self.name other.nameclass GoodKey:def __init__(self, name):self.name namedef __eq__(self, other):return isinstance(other, GoodKey) and self.name other.namedef __hash__(self):# 只把「参与比较」的字段揉进哈希保证相等则哈希相同return hash(self.name)# hash(BadKey(a)) # TypeError: unhashable typeprint(hash(GoodKey(a)) hash(GoodKey(a))) # True__eq__的默认实现比较的是同一性is__ne__默认委托给__eq__并把结果取反除非__eq__返回了NotImplemented。另外比较方法可以返回NotImplemented表示「我不会比这一对参数」Python 会再去试对方的反射方法。如果你想让一个类彻底不支持哈希正确做法是在类里写__hash__ None而不是写一个 raise 的__hash__——后者会被Hashable误判为可哈希。四、可调用与容器协议__call__让实例像函数一样被调用写obj(...)大致等价于type(obj).__call__(obj, ...)。容器协议有明确的回落顺序这是理解in、for、len()的关键语法优先用缺省时回落到len(obj)__len__没有回落直接报错for x in obj__iter__老式序列协议__getitem__x in obj__contains__先试__iter__再试__getitem__bool(obj)__bool____len__返回 0 即为假reversed(obj)__reversed____len____getitem__还有一条反直觉的规则把特殊方法设为None表示该操作不可用。语言参考举例说一个类如果把__iter__设成None它就不是可迭代的iter()会抛TypeError而且不会回落到__getitem__。# 适用于 Python 3.8class Bag:def __init__(self, items):self._items list(items)def __len__(self):return len(self._items)def __getitem__(self, index):return self._items[index] # 支持 b[0] 和切片def __contains__(self, value):return value in self._items # 比逐个遍历更快更直观def __iter__(self):return iter(self._items)def __call__(self, index):return self._items[index]bag Bag([10, 20, 30])print(len(bag), bag[1], 20 in bag, list(bag), bag(2))五、上下文管理器与属性访问with语句需要一个实现了__enter__/__exit__的对象__enter__的返回值会绑定到as后面的名字__exit__(exc_type, exc_value, traceback)在退出时被调用返回真值表示「吞掉异常」否则异常继续向外传播。没有异常发生时三个参数都是None。语言参考特别提醒__exit__不应该重新抛出传入的异常那是调用方的职责。属性访问有两个钩子区别是理解它们的重点方法调用时机用途__getattr__(self, name)只在常规查找失败之后即__getattribute__抛了AttributeError才调用兜底给不存在的属性返回动态值__getattribute__(self, name)拦截所有属性访问完全接管属性查找容易写成死递归__getattr__里如果又去访问self.xxx而该属性仍不存在就会再次触发__getattr__形成无限递归最后RecursionError。__getattribute__里再访问任何属性都会再次进入自己写错更危险——必须用super().__getattribute__(name)或object.__getattribute__(self, name)走正常路径。# 适用于 Python 3.8class LazyConfig:def __init__(self, data):self._data dict(data)def __getattr__(self, name):# 只有常规查找失败才会到这里名字不存在时抛 AttributeError 而不是死递归try:return self._data[name]except KeyError:raise AttributeError(name) from Nonecfg LazyConfig({host: localhost})print(cfg.host) # 走 __getattr__print(cfg._data) # 命中实例字典不会走 __getattr__六、__slots__省内存与限制属性__slots__可以是一个字符串、可迭代对象或字符串序列它显式声明实例会有哪些数据成员并阻止自动创建__dict__和__weakref__。语言参考列出的注意点里最容易踩的有三条从没有__slots__的类继承时子类实例仍然会有__dict__给未列在__slots__里的名字赋值会抛AttributeError__slots__是用描述符实现的所以类属性不能给 slot 变量当默认值。# 适用于 Python 3.8class SlotPoint:__slots__ (x, y)def __init__(self, x, y):self.x xself.y ysp SlotPoint(1, 2)print(sp.x, sp.y)# sp.z 3 # AttributeError: SlotPoint object has no attribute z七、一个反直觉但重要的事实特殊方法查的是类型语言参考里专门有一节叫「特殊方法查找」给了这个例子给实例挂一个__len__len()仍然不认。 class C:... pass... c C() c.__len__ lambda: 5 len(c)Traceback (most recent call last):File stdin, line 1, in moduleTypeError: object of type C has no len()原因是__hash__、__repr__这类方法在类型对象身上也存在如果按常规查找走实例字典在类型对象上调用时就会失效。所以隐式调用一律查类型。魔术方法必须定义在类上写在实例上一个也不生效。常见坑点❌ 给实例挂obj.__len__ lambda: 5就想让len(obj)生效。✅ 隐式调用只查类型魔术方法必须定义在类里。❌ 覆写了__eq__却不写__hash__。✅__hash__会被隐式置为None实例变得不可哈希进不了set、当不了dict键。❌ 写一个「抛异常的__hash__」来表示不可哈希。✅ 那会被Hashable误判为可哈希正确做法是写__hash__ None。❌__init__里写了return self或return 0。✅__init__返回非None会抛TypeError它只负责初始化、不负责返回。❌ 以为不可变类型的子类可以靠__init__改值。✅ int / str / tuple 的值在__new__阶段就定了要定制必须在__new__里做。❌ 只定义__str__然后在列表里调试发现显示的是内存地址。✅ 容器打印用repr面向开发者的__repr__一定要写。❌__getattr__里访问一个不存在的属性。✅ 会再次触发__getattr__无限递归直到RecursionError不存在时应抛AttributeError。❌ 以为把__iter__设成None还会回落到__getitem__。✅ 设为None就是明确禁用该操作iter()直接抛TypeError。总结分组方法关键机制生命周期__new__/__init__先造后初始化__init__只能返回None表示__repr__/__str__无__str__时str()退回__repr__容器用 repr比较__eq__/__hash__覆写__eq__会把__hash__置为None调用__call__实例像函数一样被调用容器__len__/__getitem__/__iter__/__contains__有明确回落顺序上下文__enter__/__exit____exit__返回真值即吞异常属性__getattr__/__getattribute__前者兜底、后者拦截一切内存__slots__阻止__dict__限制属性名魔术方法的价值在于「让你写的类用起来像内置类型」。但它们的规则是由语法固定死的不是约定俗成的风格问题查类型不查实例、__eq__与__hash__联动、容器协议有回落顺序、None表示禁用。这几条记牢比背下一张方法清单有用得多。

相关新闻

从零到一页专业简历:7步完整教程教你用LaTeX简历模板resume-template(ATS关键词+双语+AI全打通)

从零到一页专业简历:7步完整教程教你用LaTeX简历模板resume-template(ATS关键词+双语+AI全打通)

【免费下载链接】resume-template The best LaTeX resume template. 项目地址: https://gitcode.com/gh_mirrors/resumetemplate3/resume-template 点击查看 免费下载 🎯 本教程手把手带你使用开源项目 resume-template(LaTeX 简历模板&…

2026/10/11 12:45:34 阅读更多 →
机器码重置背后的授权绑定机制:从硬件指纹到License管理

机器码重置背后的授权绑定机制:从硬件指纹到License管理

1. 为什么机器码会突然“变脸”1.1 那次求助的完整经过前两天技术交流群里有人抛出一个问题,标题就一句话:“一键重置机器码!问题解决了”。乍看像某个工具的广告,但点进去才知道是群友在求助:某款行业软件绑定了机器码…

2026/10/11 12:45:34 阅读更多 →
多网段互通实验:静态路由配置与回程路由排错全攻略

多网段互通实验:静态路由配置与回程路由排错全攻略

多网段互通实验,这个题目在网工学习路线里出现频率相当高。一提起静态路由配置实验,很多新手第一反应就是“两边配个静态路由就能通”,但真上手一测,要么单向通,要么中间某个网段压根出不去。这个实验之所以值得单独拿…

2026/10/11 12:45:34 阅读更多 →

最新新闻

Boss直聘数据采集与可视化:Python爬虫实战指南

Boss直聘数据采集与可视化:Python爬虫实战指南

简介:基于Python的Boss直聘岗位数据采集与分析可视化项目,面向计算机相关专业的学生及需要实战练习的Python学习者,由导师指导完成、评审99分,代码完整可运行,适合作为课程设计、期末大作业或毕业设计参考,…

2026/10/11 13:36:02 阅读更多 →
Zephyr中国跨国并购数据清洗实战:Python处理字段、币种与去重

Zephyr中国跨国并购数据清洗实战:Python处理字段、币种与去重

简介:这份资源为Zephyr数据库导出的中国跨国并购交易数据合集,时间跨度覆盖2000年至2024年,面向从事国际商务、产业经济、金融研究的高校师生与分析师,可用于跨国并购趋势分析、案例筛选与实证研究。压缩包共2个文件,以…

2026/10/11 13:36:02 阅读更多 →
C语言链表从入门到实操:指针、内存管理与增删实现

C语言链表从入门到实操:指针、内存管理与增删实现

写这篇文章的起因有点现实——我见过太多初学者把链表当成"面试背题",却在真正需要它的时候手足无措。链表是 C 语言里绕不开的核心数据结构,它和数组一起,构成理解更复杂数据结构(栈、队列、树、图)的两根拐…

2026/10/11 13:36:02 阅读更多 →
TLS 1.3配置审计、证书锁定绕过与中间人攻击实战全记录

TLS 1.3配置审计、证书锁定绕过与中间人攻击实战全记录

我们内部做了一次针对某业务系统的传输安全深度评估,范围限制在传输层,核心任务就三条:把 TLS 1.3 的配置翻个底朝天、试着绕过证书锁定、走一遍中间人攻击的标准套路。说实话,这类活儿在安全圈里不算少见,但真正跑完一…

2026/10/11 13:36:02 阅读更多 →
Unity UI形状与分辨率适配:从锚点到SafeArea

Unity UI形状与分辨率适配:从锚点到SafeArea

Unity开发里,自定义游戏界面形状/分辨率是个埋了很多暗坑的主题。我前阵子接手一个模拟项目X,美术给了一套圆形小地图、圆角按钮和不规则面板的设计稿,第一版是在固定模拟分辨率下做出来的,看着很正常。结果一上真机,长…

2026/10/11 13:36:02 阅读更多 →
Java 实现超大附件上传:分片、断点续传与合并校验实战

Java 实现超大附件上传:分片、断点续传与合并校验实战

很多做文件上传功能的同学,第一次接到“超大附件”需求时都以为只是加个参数、调大内存就能搞定。结果一跑真实文件,几百 MB 可能还能撑住,到了几个 GB 甚至十几个 GB,要么请求超时,要么服务端内存直接打满&#xff0c…

2026/10/11 13:35:01 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/10 10:38:42 阅读更多 →