Python的字符串、列表和数字之所以好用,一半在于能保存数据,一半在于可以用熟悉的方式操作这些类型,比如用print()显示内容,用len()获取长度,用+完成相加,用for逐项遍历。定义一个类时,自然也希望这个类的对象能用上这些写法,而不必为每种操作另起一个方法名。
这些写法背后都对应着Python预先约定好的一组特殊方法名,也就是魔法方法。基本结构很固定,方法名由前后各两个下划线包住,例如__len__和__add__,定义在类里,由Python在特定操作发生时自动调用。print()会去找__str__,len()会去找__len__,+会去找__add__。类里实现了哪个方法,对象就接上了哪种写法,没实现,对应操作就会报错。
这样做的好处是让对象更容易使用,也让代码更容易理解。类的作者可以把具体实现留在内部,只向外提供一致的行为。没有魔法方法时,只能给每个操作另起名字:
python
class Shelf:
def __init__(self, books):
self.books = books
def count(self):
return len(self.books)
def get(self, i):
return self.books[i]
shelf = Shelf(['a', 'b'])
shelf.count() # 2
shelf.get(0) # 'a'
# 下面四行都会抛TypeError,注释掉后整段仍可运行
# len(shelf)
# shelf[0]
# [x for x in shelf]
# sorted(shelf)
有了魔法方法,对象就能直接用语言已有的写法:
python
class Shelf:
def __init__(self, books):
self.books = books
def __len__(self):
return len(self.books)
def __getitem__(self, i):
return self.books[i]
def __iter__(self):
return iter(self.books)
shelf = Shelf(['a', 'b'])
print(len(shelf)) # 2
print(shelf[0]) # a
print([x for x in shelf]) # ['a', 'b']
print(sorted(shelf)) # ['a', 'b']
从上面例子可以看到,魔法方法让自定义类无缝融入Python生态,没有魔法方法,类行为笨拙,要记各种专用方法名,也无法与内置函数和语法协作。

Python提供的魔法方法很多,覆盖了创建与显示、容器、运算、上下文管理、属性访问、类型与保存等不同场景。下面结合这些场景,说明各自有哪些魔法方法,以及该怎么用。更详细的介绍见Python魔法方法全面详解和Python中魔法函数全面系统详解。
目录
- [1 相关概念](#1 相关概念)
- [2 参考](#2 参考)
1 相关概念
1.1、创建与显示
对象从创建到销毁的过程中,创建、显示、销毁各有对应的魔法方法:
__new__:真正把对象造出来。__init__:对象造出来后,往里塞初始数据。__str__:print时显示给人看的内容。__repr__:调试时显示的对象样子。__format__:f"{obj:...}"里冒号后面的格式由它管。__bytes__:bytes(obj)时怎么变成字节串。__del__:对象被回收前通知一下,但不一定马上发生,也不保证一定发生。

示例代码如下:
python
class Demo:
def __new__(cls, x): # 造对象
print("__new__:造对象"); return super().__new__(cls)
def __init__(self, x): # 塞初始数据
print("__init__:塞初始数据"); self.x = x
def __str__(self): # print给人看
return f"我是Demo,x={self.x}"
def __repr__(self): # 调试看
return f"Demo(x={self.x!r})"
def __format__(self, spec): # 管f"{obj:...}"冒号后的格式
return str(self) if spec == "" else f"x={self.x:{spec}}"
def __bytes__(self): # bytes(obj)
return str(self.x).encode()
def __del__(self): # 回收前通知,不保证时机
print("__del__:对象快没了")
d = Demo(42)
print(d) # __str__:我是Demo,x=42
print(repr(d)) # __repr__:Demo(x=42)
print(f"{d:.2f}") # __format__:x=42.00
print(bytes(d)) # __bytes__:b'42'
del d # __del__:对象快没了
1.2、容器
对象除了能创建、显示,还可以像列表或字典一样被使用,靠的就是下面这些方法:
__len__:有多长,不能是负数。__getitem__:按下标或键取值,切片时拿到slice。__setitem__:按下标或键写入。__delitem__:按下标或键删除。__contains__:在不在里面。__iter__:交出一个迭代器。__next__:取下一项,取完报StopIteration。__reversed__:反向遍历用的迭代器。__missing__:字典缺键时给个默认值。__length_hint__:给个数量估计,只对没有__len__的迭代器生效。

示例代码如下:
python
class BoxIter: # 独立迭代器,游标不落在Box实例上
def __init__(self, keys):
self.keys = list(keys)
self.i = 0
def __iter__(self):
return self
def __next__(self): # 取下一项,取完报StopIteration
if self.i >= len(self.keys):
raise StopIteration
self.i += 1
return self.keys[self.i - 1]
class Box(dict): # 像字典/列表一样用
def __len__(self): # 长度
return dict.__len__(self)
def __getitem__(self, key): # 取值,切片时key是slice
return dict.__getitem__(self, key)
def __setitem__(self, key, value): # 写入
dict.__setitem__(self, key, value)
def __delitem__(self, key): # 删除
dict.__delitem__(self, key)
def __contains__(self, key): # in
return dict.__contains__(self, key)
def __iter__(self): # 交出迭代器
return BoxIter(dict.keys(self))
def __reversed__(self): # 反向遍历用的迭代器
return reversed(list(dict.keys(self)))
def __missing__(self, key): # 字典缺键时给个默认值
return 0
def __length_hint__(self): # 数量估计,Box有__len__,实际走不到这里
return dict.__len__(self)
b = Box({"a": 1, "b": 2})
print(len(b)) # __len__:2
print(b["a"]) # __getitem__:1
b["c"] = 3 # __setitem__
print(b["c"]) # 3
del b["c"] # __delitem__
print("a" in b) # __contains__:True
print(list(b)) # __iter__ + __next__:['a', 'b']
print(list(reversed(b))) # __reversed__:['b', 'a']
print(b["x"]) # __missing__:0
print(b.__length_hint__()) # __length_hint__:2,直调才看得到
1.3、运算与比较
对象除了能创建、显示、当容器用,还能像数字一样参与运算和比较:
算术
常见的有正向、反向、原地修改三类,反向用__rxxx__,原地修改用__ixxx__。
__add__、__sub__、__mul__:加、减、乘。__truediv__、__floordiv__、__mod__、__pow__:真除、整除、求余、乘方。__divmod__:商和余数一起算。
比较与真假
__eq__、__ne__:相等、不相等。__lt__、__le__、__gt__、__ge__:小于、小于等于、大于、大于等于。__bool__:真假,只能返回bool。__hash__:算哈希,相等的对象结果要一样。
单目
__neg__、__pos__、__abs__、__invert__:取负、取正、绝对值、按位取反。
转换
__int__、__float__:变整数、变浮点。__index__:当整数用,下标与hex都认。__round__、__floor__、__ceil__:四舍五入、向下取整、向上取整。

示例代码如下:
python
class Money: # 像数字一样参与运算
def __init__(self, v): # 塞初始数据
self.v = v
def __add__(self, o): # a + b
return Money(self.v + o.v)
def __eq__(self, o): # a == b
if not isinstance(o, Money):
return NotImplemented
return self.v == o.v
def __lt__(self, o): # a < b
if not isinstance(o, Money):
return NotImplemented
return self.v < o.v
def __bool__(self): # bool(a)
return self.v != 0
def __hash__(self): # hash(a),v可变时哈希跟着变,真实代码应避免
return hash(self.v)
def __neg__(self): # -a
return Money(-self.v)
def __abs__(self): # abs(a)
return Money(abs(self.v))
def __int__(self): # int(a)
return int(self.v)
def __float__(self): # float(a)
return float(self.v)
def __round__(self, n=None): # round(a)、round(a, n)
return Money(round(self.v)) if n is None else Money(round(self.v, n))
def __repr__(self): # 调试看
return f"Money({self.v!r})"
a, b = Money(10), Money(3)
print(a + b) # __add__:Money(13)
print(a == b, a < b) # __eq__、__lt__:False False
print(bool(a), bool(Money(0))) # __bool__:True False
print(hash(a) == hash(Money(10))) # __hash__:True
print(-a, abs(Money(-5))) # __neg__、__abs__:Money(-10) Money(5)
print(int(a), float(a)) # __int__、__float__:10 10.0
print(round(Money(3.14159), 2)) # __round__:Money(3.14)
1.4、调用与上下文
对象除了能运算、比较,还能像函数一样被调用,像文件一样被with管理,像异步对象一样被await和async for使用。
__call__:让实例能加括号调用__enter__:进with时执行__exit__:离开with时执行__await__:给await用的迭代器__aiter__:交出异步迭代器__anext__:异步取下一项,取完报StopAsyncIteration__aenter__:异步版的进入__aexit__:异步版的退出

示例代码如下:
python
import asyncio
class F: # __call__
def __call__(self, x): return x * 2
class C: # __enter__ / __exit__
def __enter__(self): print("进"); return self
def __exit__(self, *exc): print("出")
class A: # __await__
def __await__(self): yield; return "完"
class AI: # __aiter__ / __anext__
def __init__(self, n): self.n, self.i = n, 0
def __aiter__(self): return self
async def __anext__(self):
if self.i >= self.n: raise StopAsyncIteration
self.i += 1; return self.i
class AC: # __aenter__ / __aexit__
async def __aenter__(self): print("异步进"); return self
async def __aexit__(self, *exc): print("异步出")
print(F()(3)) # __call__:6
with C(): pass # __enter__、__exit__:进 出
async def main():
print(await A()) # __await__:完
print([x async for x in AI(3)]) # __aiter__、__anext__:[1, 2, 3]
async with AC(): pass # __aenter__、__aexit__:异步进 异步出
asyncio.run(main())
1.5、属性访问
对象除了能被调用、管理上下文,属性怎么读、写、删也可以由类接管:
__getattr__:查不到属性时兜底。__getattribute__:属性一被读就先过这一关。__setattr__:属性一被赋值就过这一关。__delattr__:属性一被删除就过这一关。__dir__:决定dir()列出什么。__get__:描述符被读时。__set__:描述符被写时。__delete__:描述符被删时。__set_name__:建类时得知字段叫什么。

示例代码如下:
python
class Model:
def show(self): # 普通方法
return "方法"
def __getattribute__(self, name): # 读属性先过这关
print("读", name)
# super()从父类找方法,Model默认父类是object
return super().__getattribute__(name)
def __getattr__(self, name): # 查不到属性时兜底
return f"默认{name}"
def __setattr__(self, name, v): # 赋值时过这关
print("写", name)
# 不能写self.xxx = v,会再进__setattr__死循环
# 这里交给父类默认逻辑真正赋值
super().__setattr__(name, v)
def __delattr__(self, name): # 删除时过这关
print("删", name)
super().__delattr__(name)
def __dir__(self): # 决定dir()列出什么
return ["x", "show"]
m = Model()
m.x = 5 # __setattr__:写x
print(m.x) # __getattribute__:读x,结果5
print(m.show()) # __getattribute__:读show,结果方法
print(m.y) # __getattribute__读y失败,转__getattr__,默认y
del m.x # __delattr__:删x
print(dir(m)) # __dir__:排序后为['show', 'x']
1.6、类型与保存
前五部分补完了对象的行为,剩下两件事,类型会一直增加,数据要离开内存。类本身就是对象,造类、认子类、复制与保存时,另有一批名字。
类与类型
__init_subclass__,有子类时通知父类。- 元类的
__call__,管类怎么造实例。 __instancecheck__,改写isinstance的结果。__subclasscheck__,改写issubclass的结果。__class_getitem__,让类能写方括号。
复制与保存
__copy__,浅复制。__deepcopy__,深复制,用memo记已经复制的。__getstate__,导出要保存的状态。__setstate__,把状态装回去。__reduce__,给出重建说明。
互操作
__fspath__,变成路径字符串。__sizeof__,单个对象占用的内存估算,sys.getsizeof会在此基础上再加对象头。
示例代码如下:
python
import copy
import pickle
import os
class Meta(type):
def __call__(cls, *a, **kw): # 元类的__call__:管类怎么造实例
print("__call__:造实例")
return super().__call__(*a, **kw)
def __instancecheck__(cls, obj): # 改写isinstance的结果
print("__instancecheck__:isinstance")
return True
def __subclasscheck__(cls, sub): # 改写issubclass的结果
print("__subclasscheck__:issubclass")
return True
class Shape(metaclass=Meta):
def __init_subclass__(cls, **kw): # 有子类时通知父类
print("__init_subclass__:新子类", cls.__name__)
super().__init_subclass__(**kw)
def __class_getitem__(cls, item): # 让类能写方括号
print("__class_getitem__:类[方括号]")
return item
class Square(Shape): # 定义时触发__init_subclass__
def __init__(self, side):
self.side = side
class Data:
def __init__(self, x):
self.x = x
def __copy__(self): # 浅复制
print("__copy__:浅复制")
return Data(self.x)
def __deepcopy__(self, memo): # 深复制,memo记已复制的
print("__deepcopy__:深复制")
return Data(copy.deepcopy(self.x, memo))
def __getstate__(self): # 导出要保存的状态
print("__getstate__:导出状态")
return {"x": self.x}
def __setstate__(self, state): # 把状态装回去
print("__setstate__:装回状态")
self.x = state["x"]
def __reduce__(self): # 给出重建说明
print("__reduce__:给重建说明")
return (Data, (self.x,))
class Path:
def __fspath__(self): # 变成路径字符串
print("__fspath__:变路径字符串")
return "/tmp"
def __sizeof__(self): # 单个对象占用的内存估算
print("__sizeof__:算体积")
return 128
s = Square(2) # 元类的__call__
isinstance(s, Shape) # __instancecheck__
issubclass(Square, Shape) # __subclasscheck__
Shape[int] # __class_getitem__
d = Data([1, 2])
copy.copy(d) # __copy__
copy.deepcopy(d) # __deepcopy__
d.__getstate__() # __getstate__
d.__setstate__({"x": 3}) # __setstate__
pickle.loads(pickle.dumps(d)) # __reduce__
os.fspath(Path()) # __fspath__
Path().__sizeof__() # __sizeof__
1.7、双下划线名称辨析
带双下划线的名字不一定都是魔法方法。魔法方法只是其中一类,前面只列了常见的,同类还有不少。另外有四类双下划线名字长得一样,却不属于魔法方法,容易混淆。
特殊属性
这些名字描述对象、类、函数或代码对象的数据与布局。语言不会在某个语法位置按这些名字自动调用对象,所以这些名字不构成特殊方法协议。
__dict__:对象或类的属性字典。__slots__:声明实例允许的属性槽位。__weakref__:让实例支持弱引用的槽位。__match_args__:模式匹配时可按位置匹配的属性名列表。__type_params__:记录泛型声明的类型参数。__firstlineno__:记录类定义的起始行号,Python3.13起可用。
代码如下:
python
class A: # 定义普通类
pass # 类体留空
a = A() # 创建实例
a.x = 1 # 给实例添加属性
print(a.__dict__) # __dict__:查看实例属性字典
class S: # 定义带__slots__的类
__slots__ = ("x",) # __slots__:只允许实例有x属性
print(S.__slots__) # 查看槽位声明
import weakref # 导入弱引用模块
class W: # 定义普通类,默认支持弱引用
pass # 类体留空
print(W.__weakref__) # __weakref__:查看弱引用槽位描述符
w = W() # 先留住引用,对象才不会被立刻回收
print(weakref.ref(w)) # 创建弱引用对象
class P: # 定义带模式匹配参数的类
__match_args__ = ("x",) # __match_args__:允许按位置匹配属性x
print(P.__match_args__) # 查看模式匹配属性名
class Box[T]: # __type_params__:定义泛型类,需要Python 3.12+
pass # 类体留空
print(Box.__type_params__) # 查看类型参数
class C: # __firstlineno__:定义类,需要Python 3.13+
pass # 类体留空
print(C.__firstlineno__) # __firstlineno__:类定义的起始行号
模块元数据
这些是模块对象的资料或导入约定,不是给自定义实例实现的特殊方法。
__all__:控制from module import *导出哪些名字。__name__:表示模块名。__file__:表示模块文件路径。__package__:表示模块所属包的上下文。
演示如下:
python
__all__ = ["foo"] # 控制from module import *只导出foo
def foo(): # 定义一个可导出函数
pass # 函数体留空
print(__all__) # 查看导出名单
print(__name__) # 查看模块名
print(__file__) # 查看模块文件路径,脚本中运行才有
print(__package__) # 查看包上下文
内置函数
这些是解释器提供的函数,可以被同名变量遮蔽,但不是供自定义实例实现的特殊方法。
__import__:执行导入。__build_class__:class语句编译后的底层入口,一般不手工调用。
示例代码如下:
python
m = __import__("math") # 用内置函数导入math模块
print(m.sqrt(4)) # 输出2.0
普通方法
这些是可以调用的普通方法,不属于特殊方法协议,不需要手工实现成语法钩子。
__subclasses__:返回类的直接子类列表。
示例如下:
python
class A: # 定义父类
pass # 类体留空
class B(A): # 定义子类
pass # 类体留空
print(A.__subclasses__()) # 查看A的直接子类列表
下划线的个数还有两套写法,与特殊方法无关。
_books:只提示内部使用,不限制访问。__books:在类体里发生名称改写,用来避免子类重名,不构成保密。class_:只是避开关键字。
代码如下:
python
_books = ["a"] # 单下划线开头,提示内部使用
print(_books) # 仍可直接访问
class C: # 定义类
def f(self): # 定义方法
self.__books = 1 # 双下划线开头,在类体里会被改名为_C__books
c = C() # 创建实例
c.f() # 调用方法,触发名称改写
print(c._C__books) # 用改写后的名字访问
# print(c.__books) # 会报AttributeError,因为名字已被改写
class_ = "避开关键字" # 单下划线结尾,避开class关键字
print(class_) # 输出 避开关键字