news 2026/6/3 8:19:36

【Python基础 | 第八篇】魔法函数(Dunder Methods)核心详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Python基础 | 第八篇】魔法函数(Dunder Methods)核心详解

【Python基础 | 第八篇】魔法函数(Dunder Methods)核心详解


前言

Python 魔法函数(Magic Methods),也叫双下方法(Dunder Methods),是那些以双下划线开头和结尾的特殊方法,如__init____str__。它们不是给你主动调用的,而是由 Python 解释器在特定场景下自动触发。理解魔法函数,是写出"Pythonic"代码的关键一步。

本文覆盖 8 类核心魔法函数,每类配独立示例类,可直接运行。


目录

  • 1. 对象生命周期:__new__ / __init__ / __del__
  • 2. 字符串表示:__str__ / __repr__
  • 3. 比较运算:__eq__ / __lt__ / __hash__
  • 4. 算术运算:__add__ / __sub__ / __mul__
  • 5. 容器协议:__len__ / __getitem__ / __setitem__ / __contains__
  • 6. 可调用对象:__call__
  • 7. 上下文管理器:__enter__ / __exit__
  • 8. 属性访问:__getattr__ / __setattr__
  • 总结

1. 对象生命周期:__new__ / __init__ / __del__

Python 创建对象分三步:创建实例 → 初始化属性 → 销毁回收,分别对应三个魔法函数。

调用顺序

阶段方法作用谁调用
1. 创建__new__分配内存,返回实例object.__new__(cls)
2. 初始化__init__给实例赋属性__new__返回后自动调用
3. 销毁__del__垃圾回收时清理解释器,不保证立即触发

cls 与 self 的区别

__new__中参数是cls,在__init__中参数是self,这二者的区别:

selfcls
指代实例(对象)本身
用在实例方法类方法(@classmethod
谁调用obj.method()MyClass.method()

代码示例

classBook:"""演示对象的创建、初始化和销毁过程"""def__new__(cls,*args,**kwargs):# __new__ 在 __init__ 之前调用,负责创建实例(返回类的实例)# 一般不需要重写,单例模式/不可变对象会用到print(f'[__new__] 创建{cls.__name__}实例')instance=super().__new__(cls)returninstancedef__init__(self,title,price):# __init__ 在实例创建后调用,负责初始化属性# Python 实例属性不需要提前声明——赋值即创建print(f'[__init__] 初始化 Book:{title}')self.title=title self.price=pricedef__del__(self):# __del__ 在对象被垃圾回收时调用(不保证立即触发)print(f'[__del__] 销毁 Book:{self.title}')
book=Book('Python 编程',59.0)# 输出:# [__new__] 创建 Book 实例# [__init__] 初始化 Book: Python 编程delbook# 主动触发销毁# 输出: [__del__] 销毁 Book: Python 编程

关于 Python 属性不需要提前声明

self.title = title这行代码本身就是在"创造"属性。Python 实例内部用__dict__字典存所有属性,赋值即创建,不需要像 Java/C++ 那样先声明。只有使用__slots__时才需要提前声明属性。

一句话总结:__new__造对象,__init__填属性,__del__收尾巴——99% 的情况你只需要写__init__


2. 字符串表示:__str__ / __repr__

这两个方法都返回字符串,但目标受众不同。

核心区别

__str____repr__
触发方式print(obj)/str(obj)repr(obj)/ 解释器直接输入变量名
目标受众用户,追求可读开发者,追求无歧义
理想效果一看就懂eval(repr(obj))重建对象

代码示例

classUser:def__init__(self,name,age):self.name=name self.age=agedef__str__(self):# 面向用户,由 print() / str() 触发returnf'用户{self.name}{self.age}岁)'def__repr__(self):# 面向开发者,由 repr() 触发,理想情况可 eval 重建returnf"User(name='{self.name}', age={self.age})"
user=User('zhupeng',23)print(str(user))# 用户 zhupeng(23 岁)print(repr(user))# User(name='zhupeng', age=23)

只定义一个选哪个?

优先__repr__。因为__str__没定义时会自动回退到__repr__,反过来不行。

一句话总结:__str__告诉用户我是谁,__repr__告诉开发者怎么重建我。


3. 比较运算:__eq__ / __lt__ / __hash__

重载比较运算符后,自定义对象可以用==<比较,用sorted()排序,放入set/ 当dict的 key。

注意事项

  • 重写__eq__后必须重写__hash__,否则对象不能放入set/ 当dict的 key
  • 返回NotImplemented而非False,让 Python 尝试反方向比较

代码示例

classScore:def__init__(self,subject,value):self.subject=subject self.value=valuedef__eq__(self,other):# == 运算符:判断两个分数是否相等ifnotisinstance(other,Score):returnNotImplementedreturnself.value==other.valuedef__lt__(self,other):# < 运算符:定义后可使用 sorted() 排序ifnotisinstance(other,Score):returnNotImplementedreturnself.value<other.valuedef__hash__(self):# 重写 __eq__ 后需要重写 __hash__returnhash((self.subject,self.value))def__repr__(self):returnf'Score({self.subject}={self.value})'
s1=Score('数学',90)s2=Score('英语',90)s3=Score('语文',80)print(s1==s2)# Trueprint(s1<s3)# Falseprint(sorted([s1,s2,s3]))# [Score(语文=80), Score(数学=90), Score(英语=90)]print({s1,s2,s3})# 可放入 set

一句话总结:想让对象能比较、能排序、能当 key?重写__eq__+__lt__+__hash__三件套。


4. 算术运算:__add__ / __sub__ / __mul__

通过算术运算符重载,让自定义对象支持+-*运算,代码更直观。

常用算术魔法函数一览

魔法函数运算符示例
__add__+v1 + v2
__sub__-v1 - v2
__mul__*v1 * 3
__truediv__/v1 / 2
__floordiv__//v1 // 2
__mod__%v1 % 2
__pow__**v1 ** 2

代码示例

classVector:"""演示算术运算符重载(二维向量)"""def__init__(self,x,y):self.x=x self.y=ydef__add__(self,other):# + 运算符:向量相加ifisinstance(other,Vector):returnVector(self.x+other.x,self.y+other.y)returnNotImplementeddef__sub__(self,other):# - 运算符:向量相减ifisinstance(other,Vector):returnVector(self.x-other.x,self.y-other.y)returnNotImplementeddef__mul__(self,scalar):# * 运算符:向量乘以标量ifisinstance(scalar,(int,float)):returnVector(self.x*scalar,self.y*scalar)returnNotImplementeddef__repr__(self):returnf'Vector({self.x},{self.y})'
v1=Vector(1,2)v2=Vector(3,4)print(v1+v2)# Vector(4, 6)print(v2-v1)# Vector(2, 2)print(v1*3)# Vector(3, 6)

一句话总结:算术魔法函数让你的对象像数字一样运算,返回NotImplemented交给 Python 处理类型不匹配。


5. 容器协议:__len__ / __getitem__ / __setitem__ / __contains__

实现这几个方法,你的对象就像内置容器一样支持len()、索引、in运算符和for遍历。

各方法触发场景

魔法函数触发方式说明
__len__len(obj)返回容器长度
__getitem__obj[index]读取元素,也支持for遍历
__setitem__obj[index] = value设置元素
__contains__item in obj成员判断

代码示例

classMyList:"""自定义容器,支持 len() / 索引 / in 运算符"""def__init__(self,items=None):self._items=list(items)ifitemselse[]def__len__(self):returnlen(self._items)def__getitem__(self,index):# 支持 for 循环遍历:Python 会从 index=0 开始调用,直到 IndexErrorreturnself._items[index]def__setitem__(self,index,value):self._items[index]=valuedef__contains__(self,item):returniteminself._itemsdef__repr__(self):returnf'MyList({self._items})'
ml=MyList([10,20,30])print(len(ml))# 3print(ml[1])# 20ml[1]=99print(20inml)# Falseprint(99inml)# Trueforiteminml:# 10 99 30print(item,end=' ')

一句话总结:实现__len__+__getitem__,你的对象就是半个列表;再加上__setitem__+__contains__,就是完整容器。


6. 可调用对象:__call__

定义__call__后,实例可以像函数一样被调用:obj()等价于obj.__call__()

代码示例

classCounter:"""让实例像函数一样被调用"""def__init__(self,start=0):self.count=startdef__call__(self,step=1):# 实例() 触发self.count+=stepreturnself.count
counter=Counter()print(counter())# 1print(counter())# 2print(counter(10))# 12

实际应用场景

  • 函数式编程:用带状态的可调用对象替代闭包
  • 装饰器:带参数的装饰器通常用类 +__call__实现
  • API 设计model(x)model.predict(x)更简洁

一句话总结:__call__让对象"可调用",模糊了函数和对象的边界。


7. 上下文管理器:__enter__ / __exit__

实现这两个方法就能用with语句,自动管理资源的获取和释放。

执行流程

with MyContext() as obj: ← 触发 __enter__,返回值赋给 obj # 使用资源 pass ← 触发 __exit__,无论是否异常

__exit__参数说明

参数含义
exc_type异常类型(无异常时为None
exc_val异常值
exc_tb异常追踪栈
返回值True吞掉异常,False/None传播异常

代码示例

classFileOpener:"""自定义 with 语句,自动管理文件资源"""def__init__(self,filename,mode='r'):self.filename=filename self.mode=mode self.file=Nonedef__enter__(self):# 进入 with 块时调用,返回值赋给 as 后的变量print(f'[__enter__] 打开文件{self.filename}')self.file=open(self.filename,self.mode,encoding='utf-8')returnself.filedef__exit__(self,exc_type,exc_val,exc_tb):# 退出 with 块时调用(无论是否异常)print(f'[__exit__] 关闭文件{self.filename}')ifself.file:self.file.close()returnFalse
withFileOpener('test.txt','w')asf:f.write('hello')# 输出:# [__enter__] 打开文件 test.txt# [__exit__] 关闭文件 test.txt

一句话总结:__enter__获取资源,__exit__释放资源,with语句保证不漏。


8. 属性访问:__getattr__ / __setattr__

这两个方法拦截属性的读取和赋值,可以实现动态属性、属性校验等高级功能。

关键区别

__getattr____getattribute__
触发时机属性不存在时才触发每次访问属性都触发
风险容易无限递归

代码示例

classConfig:"""动态属性访问,类似字典但支持点号语法"""def__init__(self):# 用 object.__setattr__ 绕过自身的 __setattr__,避免无限递归object.__setattr__(self,'_data',{})def__getattr__(self,name):# 只有访问不存在的属性时才触发ifnameinself._data:returnself._data[name]raiseAttributeError(f'Config 没有属性:{name}')def__setattr__(self,name,value):# 任何属性赋值都会触发,包括 self.x = 1self._data[name]=valuedef__repr__(self):returnf'Config({self._data})'
cfg=Config()cfg.host='localhost'# 触发 __setattr__cfg.port=8080print(cfg.host)# 触发 __getattr__ → localhostprint(cfg.port)# 8080print(cfg)# Config({'host': 'localhost', 'port': 8080})

为什么要用object.__setattr__

__init__self._data = {}会触发__setattr__,而__setattr__内部又访问self._data,此时_data还不存在,导致无限递归。object.__setattr__(self, '_data', {})直接绕过自定义的__setattr__,在底层设置属性。

一句话总结:__getattr__拦截"找不到的属性",__setattr__拦截"所有赋值"——注意用object.__setattr__避开递归陷阱。


总结

8 类魔法函数速查表

分类魔法函数核心用途
对象生命周期__new__/__init__/__del__创建、初始化、销毁对象
字符串表示__str__/__repr__用户可读 / 开发者无歧义
比较运算__eq__/__lt__/__hash__比较、排序、可哈希
算术运算__add__/__sub__/__mul__自定义 +、-、* 行为
容器协议__len__/__getitem__/__setitem__/__contains__让对象像容器一样使用
可调用对象__call__让实例像函数一样调用
上下文管理器__enter__/__exit__支持with语句
属性访问__getattr__/__setattr__拦截属性读写

使用原则

  1. 不要滥用:魔法函数是给 Python 解释器调用的,不要在代码里主动obj.__str__(),应该用str(obj)
  2. 返回NotImplemented:运算符重载遇到类型不匹配时,返回NotImplemented而非False,让 Python 尝试反向操作
  3. 成对重写:重写__eq__必须重写__hash__;优先写__repr__而非__str__
  4. 注意递归__setattr__内部赋值会再次触发自己,用object.__setattr__绕过

如果用一句话总结:魔法函数是 Python 和你的对象之间的"契约"——你实现这些方法,Python 就知道如何创建、打印、比较、运算和销毁你的对象。


如果觉得这篇文章对你有帮助,欢迎 点赞 + 收藏 + 评论,你的支持是我持续写作的动力!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 8:19:29

开发提效实战:用快马AI生成workbuddy效率工具模块化代码骨架

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 请生成一个增强版workbuddy效率工具的代码骨架&#xff0c;重点提升开发效率。要求&#xff1a;1、使用模块化JavaScript&#xff08;ES6模块&#xff09;组织代码&#xff0c;分离…

作者头像 李华
网站建设 2026/6/3 8:19:10

SAMMO框架:自动化提示词优化,提升大语言模型应用效能

1. 项目概述&#xff1a;当提示词工程遇上“自动化”如果你和我一样&#xff0c;在过去一年里深度使用过大语言模型&#xff0c;那你一定经历过这样的时刻&#xff1a;为了一个复杂的任务&#xff0c;你精心构思了一个提示词&#xff0c;满怀期待地发送给模型&#xff0c;结果得…

作者头像 李华
网站建设 2026/6/3 8:16:16

STM32F103ZET6双协议RGB灯带驱动工程:WS2811+SM16703P呼吸效果开箱即用

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;基于STM32F103ZET6主控的完整LED驱动工程&#xff0c;原生支持WS2811单线归零码和SM16703P双线时序两种协议&#xff0c;实测波形达标&#xff0c;上电即跑无需调时序。工程采用标准Keil MDK结构&#xff0c;含…

作者头像 李华
网站建设 2026/6/3 8:14:52

异步FIFO深度不足导致图像撕裂应如何量化计算最小值?

异步FIFO深度不足导致的图像撕裂&#xff0c;其根本原因在于写时钟域&#xff08;数据生产端&#xff0c;如摄像头捕获&#xff09;的瞬时数据率超过了读时钟域&#xff08;数据消费端&#xff0c;如显示控制器&#xff09;的可持续读取能力&#xff0c;导致FIFO缓冲区被写满后…

作者头像 李华
网站建设 2026/6/3 8:13:09

Haptic PIVOT:基于LRA阵列的矢量力反馈控制器设计与实现

1. 项目概述&#xff1a;当控制器有了“物理灵魂”最近在捣鼓一个挺有意思的玩意儿&#xff0c;我管它叫“Haptic PIVOT”。这名字听着有点玄乎&#xff0c;但核心想法其实很直接&#xff1a;我们能不能让手里的游戏手柄、遥控器或者VR控制器&#xff0c;不再只是一个发送指令的…

作者头像 李华