魔术方法完全参考
魔术方法(Magic Methods),也称双下方法(Dunder Methods),是 Python 中以双下划线开头和结尾的特殊方法。Python 解释器在特定操作时自动调用这些方法,通过实现它们可以让自定义类与 Python 的内置语法和协议无缝集成。 创建实例时最先调用的静态方法,负责分配内存并返回新实例。 返回值:返回新创建的实例(通常是 cls 的实例)。若不返回 cls 的实例,则不会调用 __init__。 触发时机:调用 MyClass(...) 时,在 __init__ 之前调用。 实例初始化方法,负责设置实例属性。 返回值:必须返回
官方文档:https://docs.python.org/3/reference/datamodel.html
适用版本:Python 3.12(2026-05-08 核实)
魔术方法(Magic Methods),也称双下方法(Dunder Methods),是 Python 中以双下划线开头和结尾的特殊方法。Python 解释器在特定操作时自动调用这些方法,通过实现它们可以让自定义类与 Python 的内置语法和协议无缝集成。
对象生命周期
__new__
创建实例时最先调用的静态方法,负责分配内存并返回新实例。
| 参数 | 类型 | 说明 |
|---|---|---|
| cls | type | 要创建实例的类 |
| *args | any | 传给 __init__ 的位置参数 |
| **kwargs | any | 传给 __init__ 的关键字参数 |
返回值:返回新创建的实例(通常是 cls 的实例)。若不返回 cls 的实例,则不会调用 __init__。
触发时机:调用 MyClass(...) 时,在 __init__ 之前调用。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
a = Singleton(1)
b = Singleton(2)
print(a is b) # True
print(a.value) # 2(__init__ 被调用了两次)
# 不可变类型的子类化必须在 __new__ 中修改值
class UpperStr(str):
def __new__(cls, value):
return super().__new__(cls, value.upper())
s = UpperStr('hello')
print(s) # 'HELLO'
__init__
实例初始化方法,负责设置实例属性。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 已由 __new__ 创建的实例 |
| *args | any | 构造函数的位置参数 |
| **kwargs | any | 构造函数的关键字参数 |
返回值:必须返回 None,否则抛出 TypeError。
触发时机:__new__ 返回 cls 实例后立即调用。
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
p = Point(1.0, 2.0)
__del__
对象被垃圾回收时调用,也称析构方法。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 即将被销毁的实例 |
返回值:无。
触发时机:对象引用计数降为 0 时(CPython)。注意:时机不确定,程序退出时可能不被调用。
class Resource:
def __init__(self, name):
self.name = name
print(f"Acquired: {self.name}")
def __del__(self):
print(f"Released: {self.name}")
r = Resource("file_handle")
del r # 打印 "Released: file_handle"
# 注意:不要依赖 __del__ 释放关键资源,应使用上下文管理器
__init_subclass__
当该类被继承时调用,可用于自动注册子类或强制接口约束。
| 参数 | 类型 | 说明 |
|---|---|---|
| cls | type | 新创建的子类 |
| **kwargs | any | 传给该方法的关键字参数 |
返回值:无。
触发时机:定义子类时(类创建阶段),而非实例化时。
class Plugin:
_registry = {}
def __init_subclass__(cls, plugin_name=None, **kwargs):
super().__init_subclass__(**kwargs)
if plugin_name:
Plugin._registry[plugin_name] = cls
print(f"Registered plugin: {plugin_name}")
class AuthPlugin(Plugin, plugin_name='auth'):
pass
class LogPlugin(Plugin, plugin_name='log'):
pass
print(Plugin._registry)
# {'auth': <class 'AuthPlugin'>, 'log': <class 'LogPlugin'>}
字符串表示
__repr__
返回对象的"官方"字符串表示,目标是能通过 eval() 重新构造对象(尽力而为)。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:str。
触发时机:repr(obj)、交互式解释器显示、f"{obj!r}"、容器打印时对元素调用。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x!r}, {self.y!r})"
p = Point(1, 2)
print(repr(p)) # Point(1, 2)
print([p]) # [Point(1, 2)](列表打印元素时用 repr)
__str__
返回对象的可读字符串表示,面向用户展示。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:str。
触发时机:str(obj)、print(obj)、f"{obj}"、f"{obj!s}"。未定义时回退到 __repr__。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x!r}, {self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(1, 2)
print(str(p)) # (1, 2)
print(repr(p)) # Point(1, 2)
__format__
支持 format() 内置函数和 f-string 的格式规范。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| format_spec | str | 格式规范字符串(如 .2f、>10 等),空字符串表示无格式要求 |
返回值:str。
触发时机:format(obj, spec)、f"{obj:spec}"。
class Money:
def __init__(self, amount, currency='CNY'):
self.amount = amount
self.currency = currency
def __format__(self, spec):
if spec == 'short':
return f"{self.amount:.0f}"
elif spec == 'full':
return f"{self.currency} {self.amount:,.2f}"
return f"{self.amount:{spec}}" if spec else str(self.amount)
m = Money(1234.5)
print(f"{m:full}") # CNY 1,234.50
print(f"{m:short}") # 1235
print(f"{m:.2f}") # 1234.50
__bytes__
返回对象的字节串表示。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:bytes。
触发时机:bytes(obj)。
class Packet:
def __init__(self, data: str):
self.data = data
def __bytes__(self):
return self.data.encode('utf-8')
p = Packet('hello')
print(bytes(p)) # b'hello'
比较运算
比较方法总览
| 方法 | 运算符 | 触发时机 |
|---|---|---|
__eq__(self, other) |
== |
a == b |
__ne__(self, other) |
!= |
a != b(未定义时默认取反 __eq__) |
__lt__(self, other) |
< |
a < b |
__le__(self, other) |
<= |
a <= b |
__gt__(self, other) |
> |
a > b |
__ge__(self, other) |
>= |
a >= b |
每个比较方法的参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 运算符左侧对象 |
| other | any | 运算符右侧对象 |
返回值:通常返回 bool,但可以返回任意值(NumPy 返回数组)。当无法与 other 比较时应返回 NotImplemented(不是 NotImplementedError),Python 会尝试调用 other 的反射方法。
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def _tuple(self):
return (self.major, self.minor, self.patch)
def __eq__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self._tuple() == other._tuple()
def __lt__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self._tuple() < other._tuple()
def __repr__(self):
return f"Version({self.major}, {self.minor}, {self.patch})"
v1 = Version(1, 2, 0)
v2 = Version(1, 3, 0)
print(v1 < v2) # True
print(v1 > v2) # False(total_ordering 自动生成)
print(sorted([v2, v1])) # [Version(1, 2, 0), Version(1, 3, 0)]
__hash__
返回对象的哈希值,使对象可用于集合或字典键。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:int。
触发时机:hash(obj)、将对象放入 set 或用作 dict 键。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
def __hash__(self):
# 基于值的哈希,与 __eq__ 保持一致
return hash((self.x, self.y))
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True
print({p1, p2}) # {Point(1, 2)}(只有一个元素)
重要陷阱:定义 __eq__ 后,Python 自动将 __hash__ 设为 None,使对象不可哈希。若要对象同时支持相等比较和哈希,必须显式定义 __hash__。
class BadPoint:
def __eq__(self, other):
return True # 定义了 __eq__ 但没定义 __hash__
p = BadPoint()
hash(p) # TypeError: unhashable type: 'BadPoint'
数值运算
二元运算符
| 方法 | 运算符 | 说明 |
|---|---|---|
__add__(self, other) |
+ |
加法 |
__sub__(self, other) |
- |
减法 |
__mul__(self, other) |
* |
乘法 |
__truediv__(self, other) |
/ |
真除法 |
__floordiv__(self, other) |
// |
整除 |
__mod__(self, other) |
% |
取模 |
__pow__(self, other) |
** |
幂运算 |
__matmul__(self, other) |
@ |
矩阵乘法 |
每个方法参数相同:
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 左操作数 |
| other | any | 右操作数 |
返回值:运算结果,无法处理时返回 NotImplemented。
反射运算符(右操作数版本)
当左操作数不支持该运算时,Python 尝试调用右操作数的反射版本:
| 方法 | 对应正向方法 |
|---|---|
__radd__(self, other) |
__add__ |
__rsub__(self, other) |
__sub__ |
__rmul__(self, other) |
__mul__ |
__rtruediv__(self, other) |
__truediv__ |
__rfloordiv__(self, other) |
__floordiv__ |
__rmod__(self, other) |
__mod__ |
__rpow__(self, other) |
__pow__ |
原地运算符
| 方法 | 运算符 | 说明 |
|---|---|---|
__iadd__(self, other) |
+= |
原地加法 |
__isub__(self, other) |
-= |
原地减法 |
__imul__(self, other) |
*= |
原地乘法 |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __radd__(self, other):
# 支持 0 + Vector(sum() 从 0 开始累加时需要此方法)
if other == 0:
return self
return NotImplemented
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
return self.__mul__(scalar)
def __iadd__(self, other):
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
return self # 原地修改,返回 self
return NotImplemented
def __neg__(self):
return Vector(-self.x, -self.y)
def __abs__(self):
import math
return math.sqrt(self.x**2 + self.y**2)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6)(通过 __rmul__)
print(-v1) # Vector(-1, -2)
print(abs(v2)) # 5.0
print(sum([v1, v2])) # Vector(4, 6)(sum 从 0 开始,触发 __radd__)
v1 += v2
print(v1) # Vector(4, 6)
一元运算符
| 方法 | 运算符 | 说明 | 返回值 |
|---|---|---|---|
__neg__(self) |
-obj |
取负 | 新对象 |
__pos__(self) |
+obj |
取正 | 新对象 |
__abs__(self) |
abs(obj) |
绝对值 | 数值 |
__invert__(self) |
~obj |
按位取反 | 整数 |
容器协议
__len__
返回容器的元素数量。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:非负整数。
触发时机:len(obj),也被 bool(obj) 用于真值判断(若未定义 __bool__)。
__getitem__、__setitem__、__delitem__
实现下标访问、赋值和删除。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| key | any | 索引或键,可以是整数、切片、字符串等 |
value(仅 __setitem__) |
any | 要设置的值 |
触发时机:obj[key]、obj[key] = value、del obj[key]。
__contains__
实现 in 运算符。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| item | any | 要查找的元素 |
返回值:bool。未定义时 Python 会遍历迭代器逐一比较。
__iter__ 和 __next__
实现迭代器协议。
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
__iter__(self) |
self | iterator | 返回迭代器对象(通常返回 self) |
__next__(self) |
self | any | 返回下一个值,耗尽时抛 StopIteration |
触发时机:iter(obj) 触发 __iter__,next(obj) 触发 __next__,for 循环自动调用两者。
__reversed__
支持 reversed() 内置函数。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:逆序迭代器。
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 __setitem__(self, index, value):
self._songs[index] = value
def __delitem__(self, index):
del self._songs[index]
def __contains__(self, song):
return song in self._songs
def __iter__(self):
return iter(self._songs)
def __reversed__(self):
return reversed(self._songs)
def __repr__(self):
return f"Playlist({self._songs!r})"
pl = Playlist(['A', 'B', 'C'])
print(len(pl)) # 3
print(pl[0]) # 'A'
print('B' in pl) # True
print(list(reversed(pl))) # ['C', 'B', 'A']
for song in pl:
print(song) # A, B, C
属性访问
__getattr__
仅在正常属性查找失败时调用(属性不存在时的兜底方法)。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| name | str | 属性名 |
返回值:属性值,或抛出 AttributeError。
触发时机:访问不存在的属性时。不会拦截已存在属性的访问。
__getattribute__
拦截所有属性访问,包括已存在的属性。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| name | str | 属性名 |
返回值:属性值,或抛出 AttributeError。
触发时机:任何属性访问都会触发,包括 self.x。
__setattr__、__delattr__
拦截属性赋值和删除。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| name | str | 属性名 |
value(仅 __setattr__) |
any | 要设置的值 |
触发时机:obj.attr = value、del obj.attr。
__dir__
自定义 dir() 的返回值。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:属性名列表(可迭代对象)。
class DynamicProxy:
def __init__(self, target):
object.__setattr__(self, '_target', target)
def __getattr__(self, name):
# 只在属性不存在时调用,代理到目标对象
return getattr(self._target, name)
def __getattribute__(self, name):
# 拦截所有访问
if name.startswith('_'):
return object.__getattribute__(self, name)
print(f"Accessing: {name}")
return object.__getattribute__(self, name)
def __setattr__(self, name, value):
if name.startswith('_'):
object.__setattr__(self, name, value)
else:
print(f"Setting: {name} = {value}")
object.__setattr__(self, name, value)
def __dir__(self):
base = list(object.__dir__(self))
target_attrs = dir(self._target)
return sorted(set(base + target_attrs))
# 关键:在 __getattribute__ 和 __setattr__ 中必须用 object.__xxx__ 访问自身属性
# 否则会导致无限递归
__getattr__ vs __getattribute__ 的核心区别:
| 特性 | __getattr__ |
__getattribute__ |
|---|---|---|
| 触发条件 | 属性不存在时 | 所有属性访问 |
| 用途 | 动态属性、代理 | 全面拦截、访问控制 |
| 无限递归风险 | 低 | 高(内部访问必须用 object.__getattribute__) |
| 性能开销 | 低(仅失败时触发) | 高(每次访问都触发) |
描述符
描述符是定义了 __get__、__set__ 或 __delete__ 的对象,通过类属性的方式管理实例属性。
__get__
| 参数 | 类型 | 说明 |
|---|---|---|
| self | descriptor | 描述符实例 |
| obj | instance / None | 访问该描述符的实例,通过类访问时为 None |
| objtype | type | 实例的类(或直接访问的类) |
返回值:属性值。
__set__
| 参数 | 类型 | 说明 |
|---|---|---|
| self | descriptor | 描述符实例 |
| obj | instance | 属性被设置的实例 |
| value | any | 要设置的值 |
返回值:无(None)。
__delete__
| 参数 | 类型 | 说明 |
|---|---|---|
| self | descriptor | 描述符实例 |
| obj | instance | 属性被删除的实例 |
返回值:无(None)。
__set_name__
类创建时调用,告知描述符其在类中的属性名。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | descriptor | 描述符实例 |
| owner | type | 拥有该描述符的类 |
| name | str | 描述符在类中的属性名 |
class Validator:
"""带类型验证的描述符"""
def __set_name__(self, owner, name):
self.name = name
self.private_name = f'_{name}'
def __get__(self, obj, objtype=None):
if obj is None:
return self # 通过类访问时返回描述符本身
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)
def validate(self, value):
pass # 子类实现
class PositiveInt(Validator):
def validate(self, value):
if not isinstance(value, int) or value <= 0:
raise ValueError(f"{self.name} must be a positive integer, got {value!r}")
class Person:
age = PositiveInt()
def __init__(self, name, age):
self.name = name
self.age = age # 触发 PositiveInt.__set__
p = Person('Alice', 30)
print(p.age) # 30
try:
p.age = -1 # ValueError: age must be a positive integer, got -1
except ValueError as e:
print(e)
# 通过类访问返回描述符本身
print(Person.age) # <__main__.PositiveInt object at ...>
描述符分类:
| 类型 | 条件 | 说明 |
|---|---|---|
| 数据描述符 | 定义了 __set__ 或 __delete__ |
优先级高于实例 __dict__ |
| 非数据描述符 | 只定义了 __get__ |
优先级低于实例 __dict__ |
上下文管理器
__enter__ 和 __exit__
实现 with 语句协议。
__enter__ 参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 上下文管理器实例 |
返回值:绑定到 as 子句变量的值(无 as 时不使用)。
__exit__ 参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 上下文管理器实例 |
| exc_type | type / None | 异常类型,无异常时为 None |
| exc_val | Exception / None | 异常实例,无异常时为 None |
| exc_tb | traceback / None | 异常的 traceback 对象,无异常时为 None |
返回值:真值表示吞掉(抑制)异常;假值(或 None)表示继续传播异常。
import time
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self # as 子句拿到 self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.perf_counter() - self.start
if exc_type is not None:
print(f"Exception occurred after {self.elapsed:.4f}s: {exc_val}")
else:
print(f"Elapsed: {self.elapsed:.4f}s")
return False # 不抑制异常
with Timer() as t:
total = sum(range(1_000_000))
print(f"Sum: {total}, Time: {t.elapsed:.4f}s")
# 抑制特定异常
class SuppressZeroDivision:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return exc_type is ZeroDivisionError # 只抑制 ZeroDivisionError
with SuppressZeroDivision():
result = 1 / 0 # 不会抛出异常
print("Continued normally")
可调用对象
__call__
使实例可像函数一样被调用。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
| *args | any | 调用时传入的位置参数 |
| **kwargs | any | 调用时传入的关键字参数 |
返回值:任意值。
触发时机:obj(...)。
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# 可调用对象与函数的区别:可以携带状态
class Counter:
def __init__(self):
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.count
c = Counter()
print(c()) # 1
print(c()) # 2
print(c.count) # 2
# 检测可调用性
print(callable(double)) # True
print(callable(42)) # False
类型系统
__class_getitem__
支持泛型类型参数语法 MyClass[type]。
| 参数 | 类型 | 说明 |
|---|---|---|
| cls | type | 类本身 |
| item | any | 方括号内的类型参数 |
返回值:通常返回 types.GenericAlias 或自定义对象。
触发时机:MyClass[int]、MyClass[str, int]。
class Stack:
def __class_getitem__(cls, item):
return f"Stack[{item.__name__}]"
print(Stack[int]) # Stack[int]
print(Stack[str]) # Stack[str]
# 实际用于类型注解
from typing import Generic, TypeVar
T = TypeVar('T')
class TypedStack(Generic[T]):
def __init__(self):
self._data: list[T] = []
def push(self, item: T) -> None:
self._data.append(item)
def pop(self) -> T:
return self._data.pop()
stack: TypedStack[int] = TypedStack()
__instancecheck__ 和 __subclasscheck__
自定义 isinstance() 和 issubclass() 的行为,定义在元类上。
| 方法 | 参数 | 触发时机 |
|---|---|---|
__instancecheck__(cls, instance) |
cls: 类, instance: 待检测对象 | isinstance(instance, cls) |
__subclasscheck__(cls, subclass) |
cls: 类, subclass: 待检测类 | issubclass(subclass, cls) |
class SupportsAddMeta(type):
def __instancecheck__(cls, instance):
return hasattr(instance, '__add__')
def __subclasscheck__(cls, subclass):
return hasattr(subclass, '__add__')
class SupportsAdd(metaclass=SupportsAddMeta):
pass
print(isinstance(1, SupportsAdd)) # True(int 有 __add__)
print(isinstance('a', SupportsAdd)) # True(str 有 __add__)
print(isinstance(None, SupportsAdd)) # False(None 没有 __add__)
序列化
__getstate__ 和 __setstate__
自定义对象的序列化和反序列化行为,用于 pickle。
__getstate__ 参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:任意可序列化对象(通常是字典),表示对象状态。
__setstate__ 参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例(尚未初始化) |
| state | any | __getstate__ 的返回值 |
返回值:无。
import pickle
class Connection:
def __init__(self, host, port):
self.host = host
self.port = port
self._socket = None # 不可序列化的资源
def connect(self):
self._socket = f"<socket to {self.host}:{self.port}>"
def __getstate__(self):
# 不序列化 socket 连接
state = self.__dict__.copy()
state['_socket'] = None
return state
def __setstate__(self, state):
self.__dict__.update(state)
# 反序列化后不自动重连
conn = Connection('localhost', 5432)
conn.connect()
print(conn._socket) # <socket to localhost:5432>
serialized = pickle.dumps(conn)
restored = pickle.loads(serialized)
print(restored._socket) # None(socket 未被序列化)
print(restored.host) # 'localhost'
__reduce__
提供完整的序列化控制,返回重建对象所需的信息。
| 参数 | 类型 | 说明 |
|---|---|---|
| self | instance | 当前实例 |
返回值:元组 (callable, args) 或 (callable, args, state, ...),callable(*args) 用于重建对象。
class MyObj:
def __init__(self, data):
self.data = data
def __reduce__(self):
return (self.__class__, (self.data,))
踩坑与注意事项
1. __eq__ 重写后 __hash__ 自动变 None
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
# 没有定义 __hash__!
p = Point(1, 2)
hash(p) # TypeError: unhashable type: 'Point'
{p: 'origin'} # TypeError: unhashable type: 'Point'
# 修复:显式定义 __hash__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
# 若要声明对象不可哈希(可变对象的最佳实践):
# __hash__ = None
2. __getattr__ 与 __getattribute__ 的无限递归
class BadProxy:
def __getattribute__(self, name):
# 错误:self.data 会再次触发 __getattribute__,导致无限递归
return self.data[name] # RecursionError!
class GoodProxy:
def __getattribute__(self, name):
# 正确:使用 object.__getattribute__ 访问自身属性
data = object.__getattribute__(self, 'data')
return data[name]
3. __del__ 的不确定性
class Resource:
def __del__(self):
# 不要在这里做关键清理
# 1. 循环引用时可能不被调用
# 2. 程序崩溃时不被调用
# 3. 解释器退出时调用顺序不确定
pass
# 正确做法:使用上下文管理器
class Resource:
def __enter__(self):
return self
def __exit__(self, *args):
self.cleanup() # 确保被调用
4. __exit__ 返回值容易搞错
class BadContextManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 错误:无论是否有异常,总是返回 True,会吞掉所有异常
return True
class GoodContextManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
print(f"Suppressed ValueError: {exc_val}")
return True # 仅抑制 ValueError
return False # 其他异常正常传播
5. __new__ 返回不同类型时不调用 __init__
class MyClass:
def __new__(cls, value):
if value < 0:
return None # 返回非 cls 实例
return super().__new__(cls)
def __init__(self, value):
print("__init__ called") # 当 value < 0 时不会执行
self.value = value
obj = MyClass(-1)
print(obj) # None,__init__ 未被调用
6. 描述符只对类属性有效
class MyDescriptor:
def __get__(self, obj, objtype=None):
return 42
class MyClass:
attr = MyDescriptor() # 类属性,描述符生效
obj = MyClass()
print(obj.attr) # 42(通过描述符)
obj.__dict__['attr'] = 100 # 直接写入实例字典
# 数据描述符(有 __set__):obj.attr 仍返回 42(描述符优先)
# 非数据描述符(无 __set__):obj.attr 返回 100(实例字典优先)
最佳实践
实现 __eq__ 必须同时实现 __hash__:Python 3 中定义 __eq__ 后,__hash__ 会被自动设为 None(对象变为不可哈希),无法作为 dict key 或 set 元素。可变对象显式设 __hash__ = None,不可变对象同时实现两者并保证 hash 一致性。
__repr__ 应返回可重现对象的字符串:理想格式是 ClassName(arg1=val1, arg2=val2),能被 eval() 重新创建对象;至少要包含足够信息让开发者调试定位问题。
用 __slots__ 减少内存占用:大量实例时,__slots__ 替代 __dict__ 存储实例属性,内存节省 20-50%;但 __slots__ 不支持默认实例属性,需要在 __init__ 中初始化所有槽。
__enter__ / __exit__ 成对实现:只要类实现这两个方法,就能用于 with 语句,无需继承任何基类;__exit__ 返回 True 时抑制异常,通常应返回 None / False。
谨慎使用 __getattr__ vs __getattribute__:__getattr__ 只在属性查找失败时调用(安全),__getattribute__ 在任何属性访问时调用(容易无限递归);在 __getattribute__ 中访问 self 属性必须用 object.__getattribute__(self, name)。
常见陷阱
陷阱:__del__ 不等于析构函数,不可靠
现象: 期望 __del__ 在对象销毁时释放资源,但资源没有被及时释放,或程序退出时才执行。
原因: __del__ 依赖 CPython 的引用计数,存在循环引用时无法预测调用时机,解释器退出时调用顺序也不确定。
解决: 资源管理用上下文管理器(with)和 __enter__/__exit__,或显式调用 close() 方法。
陷阱:__init_subclass__ 替代元类
现象: 用元类(metaclass)拦截子类定义时代码复杂难维护。
原因: 很多元类使用场景可以用 Python 3.6+ 的 __init_subclass__ 实现,更简洁。
解决: 当需要在子类定义时执行逻辑时,优先考虑 __init_subclass__,只有需要拦截 type() 调用本身时才用元类。
陷阱:__iadd__ 返回值决定变量是否重绑定
现象: list += [1] 不改变变量绑定(原地修改),但 tuple += (1,) 创建新对象并重绑定变量。
原因: += 调用 __iadd__,若返回 self(如 list),变量不重绑定;若返回新对象(如 tuple),变量重绑定到新对象。
解决: 实现 __iadd__ 时原地修改并 return self,保持与内置 list 行为一致。