第一次看到__init__这种写法时,我也觉得挺神秘的——两个下划线包着单词,像被施了魔法一样。后来才知道,Python里所有用双下划线包裹的方法都叫魔法函数(Magic Methods),官方文档称之为"特殊方法",老外开发者更喜欢叫"Dunder Methods"(Double Under的缩写)。这些函数之所以特殊,是因为它们能让你的自定义类拥有和内置类型一样的行为。
举个例子,当你写len(my_list)时,Python实际上调用了my_list.__len__();当你用print(obj)输出对象时,真正执行的是obj.__str__()。这种机制让Python的语法保持简洁的同时,又给开发者提供了强大的扩展能力。我在开发电商系统时,就曾用__getitem__让商品类支持下标访问,用__contains__实现快速库存检查,代码可读性直接提升了一个档次。
要查看对象支持的魔法函数,最直接的方法是使用dir()函数。试试在Python解释器里输入:
python复制dir(int) # 查看整数类型的魔法函数
dir(str) # 查看字符串类型的魔法函数
你会发现这些内置类型都实现了数十个魔法函数,正是它们支撑起了我们日常使用的各种语法糖。
__init____init__可能是最广为人知的魔法函数了,但它其实不是真正的构造函数。准确地说,它是个初始化方法——当__new__完成对象创建后,__init__才被调用来设置初始状态。我在开发爬虫框架时,经常这样设计类:
python复制class Spider:
def __init__(self, start_urls=None):
self.start_urls = start_urls or []
self.visited = set()
这里的关键点是__init__不应该返回任何值(除了None),它的职责就是初始化实例属性。
__str__和__repr__调试代码时,你肯定见过<__main__.Person object at 0x10d8f3d90>这种默认输出。通过实现__str__,我们可以让对象打印出有意义的信息:
python复制class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
return f"{self.name} (${self.price:.2f})"
print(Product("Python书", 99.8)) # 输出: Python书 ($99.80)
而__repr__则应该返回更精确的、通常是能用于重建对象的表达式。好的实践是:
python复制def __repr__(self):
return f"Product({self.name!r}, {self.price!r})"
在Jupyter Notebook等交互环境中,__repr__的输出会被直接显示,所以它比__str__更重要。
要实现对象比较,Python提供了六个魔法函数:
__lt__ (<)__le__ (<=)__eq__ (=)__ne__ (!=)__gt__ (>)__ge__ (>=)但实际项目中,我推荐使用functools.total_ordering装饰器,只需实现__eq__和其中一个比较方法即可:
python复制from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __eq__(self, other):
return self.grade == other.grade
def __lt__(self, other):
return self.grade < other.grade
这样就能自动获得所有比较操作的支持,代码量减少一半。
实现了比较魔法函数后,你的对象就能直接用在sorted()、min()、max()等函数中了。我在处理电商订单时这样设计:
python复制class Order:
def __init__(self, order_id, amount, create_time):
self.order_id = order_id
self.amount = amount
self.create_time = create_time
def __lt__(self, other):
# 优先按金额降序,其次按时间升序
if self.amount != other.amount:
return self.amount > other.amount
return self.create_time < other.create_time
现在sorted(orders)就能按照业务需求正确排序了,比写key=lambda函数更直观。
要让自定义类支持类似列表的操作,这三个魔法函数是基础:
python复制class Playlist:
def __init__(self, songs):
self.songs = list(songs)
def __len__(self):
return len(self.songs)
def __getitem__(self, index):
return self.songs[index]
def __contains__(self, song):
return song in self.songs
现在你的Playlist类就支持len(playlist)、playlist[3]甚至切片操作playlist[1:5]了。我在开发音乐播放器时,正是用这种方式让播放列表与内置列表无缝衔接。
如果想实现可变序列,还需要__setitem__和__delitem__:
python复制def __setitem__(self, index, value):
self.songs[index] = value
def __delitem__(self, index):
del self.songs[index]
加上这两个方法后,你的类就能支持playlist[2] = new_song和del playlist[1]这样的操作了。完整实现这些方法后,很多Python内置函数和库会自动适配你的类。
__iter__和__next__的魔法Python的for循环背后是迭代器协议。要让类支持迭代,需要实现__iter__:
python复制class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
num = self.current
self.current -= 1
return num
现在你可以用for num in Countdown(5):来倒数了。我在开发游戏时,用这种方式实现了各种特效序列的播放控制。
更Pythonic的写法是使用生成器函数:
python复制def __iter__(self):
for song in sorted(self.songs, key=lambda s: s.play_count, reverse=True):
yield song
这种方式自动实现了__next__,代码更简洁。我在实现音乐播放器的"热门歌曲"功能时就采用了这种方案。
通过实现__add__、__sub__等方法,可以让对象支持数学运算:
python复制class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
现在你可以写v3 = v1 + v2这样的自然语法了。我在开发游戏引擎时,大量使用这种技术来处理坐标运算。
有时候你需要处理数字 * 向量这样的情况,这时需要实现__rmul__:
python复制def __rmul__(self, scalar):
return self.__mul__(scalar)
这样无论是向量 * 3还是3 * 向量都能正确工作。
__enter__和__exit__的优雅with语句的背后就是这两个魔法函数:
python复制class DatabaseConnection:
def __enter__(self):
self.conn = connect_to_database()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()
if exc_type is not None:
logger.error(f"Database error: {exc_val}")
现在可以这样使用:
python复制with DatabaseConnection() as conn:
conn.execute("SELECT...")
连接会自动关闭,异常也会被处理。我在各种需要资源管理的场景都采用这种模式。
__exit__方法接收三个异常相关参数,你可以根据业务需要决定是否处理异常:
python复制def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()
if exc_type is ValueError:
logger.warning("Ignoring ValueError")
return True # 抑制异常
返回True表示异常已被处理,不会继续传播。
__getattr__的高级用法当访问不存在的属性时,__getattr__会被调用:
python复制class DynamicConfig:
def __getattr__(self, name):
if name in self._config:
return self._config[name]
raise AttributeError(f"No such config: {name}")
我在开发配置系统时用这种方式实现了动态配置项访问,同时保持IDE的代码提示功能。
完整的属性访问魔法函数包括:
__getattribute__:所有属性访问都会经过它__setattr__:设置属性时调用__delattr__:删除属性时调用使用这些方法时要小心无限递归问题,通常需要通过super()来访问真正的属性存储:
python复制def __setattr__(self, name, value):
if name == "readonly":
raise AttributeError("readonly属性不能修改")
super().__setattr__(name, value)
__call__的妙用实现__call__可以让实例像函数一样被调用:
python复制class Adder:
def __init__(self, base):
self.base = base
def __call__(self, x):
return self.base + x
add5 = Adder(5)
print(add5(3)) # 输出8
我在实现插件系统时,用这种方式让插件类同时保持状态又可以被直接调用。
__call__最常见的用途之一是创建有状态的装饰器:
python复制class Retry:
def __init__(self, max_retries=3):
self.max_retries = max_retries
def __call__(self, func):
def wrapper(*args, **kwargs):
for _ in range(self.max_retries):
try:
return func(*args, **kwargs)
except Exception:
continue
raise RuntimeError("Max retries exceeded")
return wrapper
现在你可以这样使用:
python复制@Retry(max_retries=5)
def call_api():
...
结合多个魔法函数,我们可以创建一个简单的ORM类:
python复制class Model:
def __init__(self, **kwargs):
self._data = kwargs
def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(name)
def __setattr__(self, name, value):
if name == '_data':
super().__setattr__(name, value)
else:
self._data[name] = value
def __iter__(self):
return iter(self._data.items())
def __len__(self):
return len(self._data)
def __str__(self):
return str(self._data)
这个简单的实现已经支持属性访问、迭代、长度计算等功能。
我们可以创建一个Fraction类来完整演示数值运算魔法函数:
python复制class Fraction:
def __init__(self, numerator, denominator=1):
self.n = numerator
self.d = denominator
def __add__(self, other):
if isinstance(other, int):
other = Fraction(other)
return Fraction(self.n * other.d + other.n * self.d, self.d * other.d)
def __sub__(self, other): ...
def __mul__(self, other): ...
def __truediv__(self, other): ...
def __neg__(self): ...
def __abs__(self): ...
def __eq__(self, other): ...
def __lt__(self, other): ...
# 其他数值运算方法...
通过完整实现这些魔法函数,Fraction类就能像内置数值类型一样参与各种数学运算。