在Python的编程世界里暗藏着一类内置的协议方法集叫魔术方法(Magic Methods)
,其核心是为了使定义的对象像内置数据类型一样自然、顺滑地调用,像我们熟悉的__init__
方法就是成员之一。
魔术方法虽然内置,但并不简单,特别是其应用场景的最佳实践,即使驰骋多年的Python开发老鸟,也不见得能够掌控精髓,自然而然会出现畏惧使用、尽量避免使用魔术方法的情况。许多Python初学者教程不过多涉及或者绕过魔术方法,一般将其作为进阶内容,也进一步佐证魔术方法较难使用的事实。
Python魔术方法更像是一柄哈耳庇厄双刃剑,使用得当会得到开发效率提升、代码简洁优雅的好处,使用不当或过度滥用则会造成复杂度显著增加、可读性差甚至影响性能的恶果。如同瓦雷利亚钢剑需要真正的史塔克血脉才能驾驭,魔术方法也要求开发者具备足够的Python内力。我们需要避免像《冰火》中所述的“当剑比人聪明时,悲剧就会发生”
复演。那么,究竟什么时候该挥动这柄魔法之剑?何时又该将其入鞘呢?
1 导入:魔术方法的本质、优点及性能
执行如下代码时,执行到print(f"len(my_cart)={len(my_cart)}")
会报TypeError: object of type 'ShoppingCart' has no len()
的错误。在Python中len()
是一个内置函数(built-in function),在底层会尝试调用目标对象的特殊方法 __len__()
,类似__len__()
这样的一类函数也即为本文主角称为魔术方法或双下划线方法(dunder method)。魔术方法的本质,其实是对象与 Python 内置函数的一种协议契约,调用len()可以简单地将过程分解为以下几个动作:
-
调用内置函数
len(obj)
-
Python 检查
obj
是否实现了__len__()
方法-
如果有,就执行
obj.__len__()
并返回其结果 -
如果没有,抛出
TypeError
-
my_list = [1,2,3]
class ShoppingCart:
def __init__(self):
self.items = []
my_cart = ShoppingCart()
print(f"len(my_list)={len(my_list)}")
print(f"len(my_cart)={len(my_cart)}") # TypeError: object of type 'ShoppingCart' has no len()
通过以上分析我们发现:len(my_list)
可以执行是因为“内置实现”了__len__()
方法。事实是否确实如此呢?我们进一步验证:
my_list = [1,2,3]
print('__len__' in dir(my_list)) # True
print(my_list.__len__()) # 3
print(hasattr(my_list, '__len__')) # True
结果显而易见,list
确实“内置实现”了__len__()
方法,这给我们日常开发提供了许多便利,在Python中这些类型提前内置了__len__()
方便我们使用len()方法进行调用:
类型 | 示例 | len() 调用结果 |
---|---|---|
list | [1, 2, 3] | 3 |
str | "hello" | 5 |
dict | {"a": 1} | 1 |
tuple | (1, 2) | 2 |
set | {1, 2, 3} | 3 |
那么我们应该如何优化自定义类ShoppingCart
使其支持len()方法调用呢?很显然是需要实现__len__()
方法的,但在这之前,我们不妨先看一个不基于魔术方法的常规实例化对象长度获取方式(简称常规方案):
class ShoppingCart:
def __init__(self):
self.items = []
def count_items(self):
return len(self.items)
cart = ShoppingCart()
cart.items.extend(['apple', 'banana', 'orange'])
print(cart.count_items()) # 输出: 3 ✅
继续使用__len__()
实现我们的需求(简称魔术方法方案),方便对比分析:
class ShoppingCart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
cart = ShoppingCart()
cart.items.extend(['apple', 'banana', 'orange'])
print(len(cart)) # 输出: 3 ✅
通过对比以上两段代码,很容易得出这些结论:
✅ __len__()
这类魔术方法并不是充分必要的,不使用也能实现需求
✅ 魔术方法方案与Python内置的list、dict、set等行为一致,代码更自然、可读性更高,调用也更顺滑(毕竟len关键字是需要占用及记忆的),代码调用语义相对统一;而常规方案,似乎在“取什么名字命名”、要记住方法名字才能调用上花费一些额外的脑力及精力
✅ 魔术方法方案更具通用性,方便外部依赖工具如分页器调用
✅ 魔术方法方案更Pythonic,毕竟Python鼓励通过实现魔术方法来提升类的行为表现能力
通过以上分析我们不难发现,使用魔术方法能让你的代码不仅能用,而且“像是 Python 自己的孩子”,让用户在使用中没有违和感、没有额外学习成本,提升了接口设计的优雅性和可读性,但是我们还需要回答最后一个问题:与常规方法相对,魔术方法在性能上是否有差异呢?花费时间、精力去研究黑魔法是否值当?
在大多数情况下,魔术方法和普通方法一样,都是通过绑定到对象的特殊名称(如 __len__
)来被调用的。Python 在调用这些方法时并没有做太多额外操作,只是语法糖背后的“转发”罢了,两者本质上在性能层面几乎没有差别。
我们使用dis
模块反汇编了两种调用方式
len(a)
是通过内置函数间接调度__len__()
,涉及一次全局函数查找 + 魔术方法查找。a.count()
是直接通过实例查找并调用方法,使用了更优化的字节码指令。
虽然 a.count()
字节码更紧凑(少一次全局函数加载),但在真实场景中性能差异几乎可以忽略不计
import dis
class A:
def __len__(self):
return 123
def count(self):
return 123
a = A()
dis.dis(lambda: len(a))
dis.dis(lambda: a.count())
我们使用timeit
模块进一步测试:需要承认魔术方法慢一点,但性能差异本身几乎可以忽略不计,至少在性能优化中魔术方法不会成为首当其冲的对象需要优化的部分
import timeit
setup = """
class ShoppingCart:
def __init__(self): self.items = list(range(1000))
def __len__(self): return len(self.items)
def count_items(self): return len(self.items)
cart = ShoppingCart()
"""
print("len(cart):", timeit.timeit("len(cart)", setup=setup, number=10**6))
print("cart.count_items():", timeit.timeit("cart.count_items()", setup=setup, number=10**6))
# len(cart): 0.8521400169993285
# cart.count_items(): 0.786086089006858
2 常用魔术方法
2.1 构造与销毁
__new__
:创建实例(★★★★☆)
对象实例化的时候所调用的第一个方法,__new__
的第一个参数必须是类;其余参数是构造函数调用中看到的参数。类方法,用于 创建并返回一个新实例,在 __init__
之前调用的。结合 __init__
一起使用时,__new__
返回的对象才会被传给 __init__
。
- ✅ 函数签名
def __new__(cls, *args, **kwargs):
# 必须返回 cls 的实例(或其子类的实例)
-
✅ 最佳实践:如果你在自定义类中没有特殊需求,通常不需要重写
__new__
。但在某些高级应用(如单例模式、元类、自定义不可变类型等)中非常有用,如:- 控制实例创建(如单例)
- 继承不可变类型(如str/tuple),不可变类型可变初始化都必须在
__new__
中完成,__init__
改不了任何东西 - 元类或类工厂
代码示例1:借助__new__
实现单例模式
class Singleton:
_instance = None
def __new__(cls,*args,**kwargs):
if cls._instance is None:
print("Creating new _instance...")
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self,value):
self.value = value
a = Singleton(10)
b = Singleton(20)
print(a is b) # 同一个实例:True
print(a.value) # 虽然是同一个示例但是__init__可以反复被执行:20
代码示例2:借助__new__
实现不可变继承
继承str在__init__方法中进行可变操作不会生效,需要在__new__完成相关操作
class UpperStrWrong(str):
def __init__(self, value):
# 尝试在 __init__ 中修改字符串(无效!)
self.value = value.upper() # 这行代码不会生效!
class UpperStrRight(str):
def __new__(cls,value):
processed_value = value.upper()
return super().__new__(cls,processed_value)
# 注意:这里不需要 __init__,因为 str 是不可变的,
# 所有初始化逻辑已经在 __new__ 中完成。
s1 = UpperStrWrong("hello")
print(s1) # hello
s2 = UpperStrRight("hello")
print(s2) # HELLO
代码示例3:元类应用
__new__
在元类(metaclass)中扮演核心角色,用于动态创建类。元类的 __new__
方法会在类定义时被调用,允许我们干预类的创建过程(例如修改类属性、添加方法等)
import time
import re
from functools import wraps
class Auth(type):
def __new__(cls,name,bases,namespace):
tb_name = namespace.get('tb_name')
if not isinstance(tb_name,str):
raise TypeError(f"类{name}必须定义字符串类型的'tb_name'属性")
if 'id' not in namespace:
raise TypeError(f"类{name}必须包含字段'id'")
namespace['latest_date'] = int(time.time())
ip = namespace.get('ip')
if ip and not cls.is_valid_ip(ip):
raise ValueError(f"类 {name} 的 IP 地址无效: {ip}")
active_method = namespace.get('active')
if not callable(active_method):
raise TypeError(f"类 {name} 必须实现名为 'active' 的方法")
# ✅ 替换 active 方法为包装器
@wraps(active_method)
def wrapper(self, *args, **kwargs):
result = active_method(self, *args, **kwargs)
if not isinstance(result, bool):
raise TypeError(f"{name}.active() 方法必须返回布尔值(True 或 False),但返回了 {type(result).__name__}")
return result
namespace['active'] = wrapper # 必须覆盖原函数
status = namespace.get('status')
if status in (0, 1):
namespace['status'] = bool(status)
return super().__new__(cls, name, bases, namespace)
@staticmethod
def is_valid_ip(ip):
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
if not re.match(pattern, ip):
return False
return all(0 <= int(part) <= 255 for part in ip.split('.'))
class User(metaclass=Auth):
tb_name = "users"
id = 101
ip = "192.168.1.1"
status = 1 # 会被自动转为 True
def active(self):
return "Hello"
print(User.latest_date) # 会打印一个时间戳
print(User.status) # True(已自动转换)
u = User()
print(u.active()) # TypeError: User.active() 方法必须返回布尔值(True 或 False),但返回了 str
__init__
:初始化实例(★★★★★)
__init__
是 Python 中最常用的魔术方法之一,在实例创建后自动调用,用于初始化新创建的对象。它是实例化过程中调用的第二个方法(在 __new__
之后),通常被称为"构造方法"(虽然严格来说 __new__
才是真正的构造方法)。
- ✅ 函数签名
def __init__(self, *args, **kwargs):
# 初始化实例属性
-
✅ 关键特性
- 初始化而非创建:
__init__
不创建实例,只负责初始化(__new__
负责创建) - 自动调用:在实例创建后自动执行
- 无返回值:不应该返回任何值(返回 None)
- 可变对象操作:适合对可变对象进行初始化操作
- 初始化而非创建:
-
✅ 最佳实践
- 只用于初始化:不要尝试在
__init__
中创建实例 - 保持简单:复杂的初始化逻辑可以考虑使用工厂方法
- 参数验证:可以在
__init__
中进行输入验证 - 避免副作用:尽量减少初始化时的外部操作
- 只用于初始化:不要尝试在
示例1:初始化及参数校验
class Circle:
def __init__(self, radius):
if not isinstance(radius, (int, float)):
raise TypeError("Radius must be numeric")
if radius <= 0:
raise ValueError("Radius must be positive")
self.radius = radius
self.area = 3.14 * radius ** 2 # 计算并缓存面积
c = Circle(5)
print(c.area) # 输出: 78.5
示例2:工厂模式替代复杂初始化
import math
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
@classmethod
def from_polar(cls, r, theta):
"""工厂方法:从极坐标创建复数"""
real = r * math.cos(theta)
imag = r * math.sin(theta)
return cls(real, imag)
# 使用极坐标工厂方法初始化
cn2 = ComplexNumber.from_polar(5, math.atan2(4, 3)) # 半径5,角度≈53.13°
print(f"极坐标转换: {cn2.real:.2f} + {cn2.imag:.2f}i")
# 输出: 极坐标转换: 3.00 + 4.00i
__del__
:对象销毁(★☆☆☆☆)
在对象被垃圾回收前自动调用。它通常用于执行一些清理工作,如关闭文件、释放资源等。如果难于把控其特性,建议慎用
- ✅ 函数签名
def __del__(self):
# 清理资源
-
✅ 关键特性
- 不确定调用时机:Python的垃圾回收机制决定何时调用
- 不保证一定执行:程序异常终止时可能不会执行
- 慎用:过度依赖可能导致资源管理混乱
-
✅ 最佳实践
- 显式资源管理优先:推荐使用
with
语句或显式close()
方法 - 一定要使用避免复杂操作:不要在
__del__
中执行耗时或可能失败的操作 - 循环引用问题:有循环引用的对象可能永远不会被
__del__
处理
- 显式资源管理优先:推荐使用
2.2 字符串表现与可视化
__str__
:面向用户的友好字符串表示(★★★★☆)
__str__
定义了当你使用 print(obj)
或 str(obj)
时对象的“可读性”表示,返回一个对终端用户友好的字符串。常用于调试或日志输出中,强调清晰、简洁。
- ✅ 函数签名
def __str__(self) -> str:
# 返回用户可读字符串
-
✅ 最佳实践
- 优先展示关键属性或摘要信息
- 返回值应始终为字符串
- 适合打印展示,而非重建对象
代码示例
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age} yrs old)"
p = Person("Alice", 30)
print(p) # Alice (30 yrs old)
__repr__
:面向开发的友好字符串表示(★★★★☆)
返回开发者视角的表示形式,其目标是明确、准确地表达该对象,以便在调试时还原或分析对象结构。通常优先用于交互式解释器、调试工具等场景。
- ✅ 函数签名
def __repr__(self) -> str:
# 返回官方/开发者友好字符串
-
✅ 最佳实践
-
应返回一段有效的表达式字符串(最好是可
eval()
重建的) -
与
__str__
不同,它面向开发人员,不是最终用户 -
在
print()
无法找到__str__
时,会退回使用__repr__
-
代码示例
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
p = Person("Bob", 25)
print(repr(p)) # Person(name='Bob', age=25)
__format__
:格式化字符串表示(★★★☆☆)
定义对象在使用 format()
函数或 f-string 格式化时的行为,允许自定义对象的格式化输出方式
- ✅ 函数签名
def __format__(self, format_spec: str) -> str:
# 返回格式化后的字符串
-
✅ 关键特性
- 灵活控制:根据格式说明符(format_spec)返回不同格式
- 兼容性:支持
format()
和 f-string - 默认行为:未定义时回退到
__str__
-
✅ 最佳实践
- 处理格式说明符:解析 format_spec 实现多格式输出
- 保持一致性:与
__str__
和__repr__
风格协调 - 类型安全:始终返回字符串
代码示例1:格式化输出
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __format__(self, format_spec):
if format_spec == 'f':
return f"{self.celsius * 9/5 + 32:.1f}°F"
elif format_spec == 'k':
return f"{self.celsius + 273.15:.1f}K"
else:
return f"{self.celsius:.1f}°C"
temp = Temperature(25)
print(f"{temp}") # 25.0°C
print(f"{temp:f}") # 77.0°F
print(f"{temp:k}") # 298.1K
代码示例2:与__str__
搭配使用
class Version:
def __init__(self, major, minor):
self.major = major
self.minor = minor
def __str__(self):
return f"v{self.major}.{self.minor}"
def __format__(self, format_spec):
if format_spec == 'long':
return f"Version {self.major}.{self.minor}"
elif format_spec == 'verbose':
return f"Major: {self.major}, Minor: {self.minor}"
else:
return str(self)
v = Version(2, 7)
print(f"{v}") # v2.7
print(f"{v:long}") # Version 2.7
print(f"{v:verbose}") # Major: 2, Minor: 7
__bytes__
:字节序列表示(★☆☆☆☆)
定义了对象在调用 bytes(obj)
时的行为,返回对象的字节序列表示形式。主要用于需要将对象序列化为字节流的场景如网络传输等,不常用不赘述
2.3 运算
常规运算
常规运算魔术方法用于实现基本的数学运算,如加减乘除等。当对象作为左操作数参与运算时自动调用。
魔术方法 | 运算符 | 触发场景 | 返回值要求 | 典型应用场景 |
---|---|---|---|---|
__add__ | + | 左操作数加法运算时调用 | 任意类型 | 数值计算、向量/矩阵运算、字符串拼接 |
__sub__ | - | 左操作数减法运算时调用 | 任意类型 | 数值计算、集合差集操作 |
__mul__ | * | 左操作数乘法运算时调用 | 任意类型 | 数值计算、序列重复 |
__truediv__ | / | 左操作数真除法运算时调用 | 任意类型 | 浮点数除法 |
__floordiv__ | // | 左操作数地板除运算时调用 | 任意类型 | 整数除法 |
__mod__ | % | 左操作数取模运算时调用 | 任意类型 | 求余数、字符串格式化 |
__pow__ | ** | 左操作数幂运算时调用 | 任意类型 | 幂运算、矩阵幂 |
__matmul__ | @ | 左操作数矩阵乘法运算时调用 | 任意类型 | 矩阵乘法 |
以__add__
(★★★☆☆)为例展开说明
实现对象的加法运算 (+
),返回运算结果。
- ✅ 函数签名
def __add__(self, other):
# 返回加法结果
-
✅ 最佳实践
- 类型检查:建议先检查 other 的类型是否兼容
- 返回新对象:通常返回新实例而非修改原对象
- 未实现处理:对于不支持的类型返回
NotImplemented
-
✅ 代码示例
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
右操作运算
当左操作数不支持运算且两个操作数类型不同时,Python 会尝试调用右操作数的反向运算方法。
魔术方法 | 运算符 | 触发场景 | 返回值要求 |
---|---|---|---|
__radd__ | + | 左操作数不支持加法时调用右操作数的此方法 | 任意类型 |
__rsub__ | - | 左操作数不支持减法时调用 | 任意类型 |
__rmul__ | * | 左操作数不支持乘法时调用 | 任意类型 |
__rtruediv__ | / | 左操作数不支持真除法时调用 | 任意类型 |
__rfloordiv__ | // | 左操作数不支持地板除时调用 | 任意类型 |
__rmod__ | % | 左操作数不支持取模时调用 | 任意类型 |
__rpow__ | ** | 左操作数不支持幂运算时调用 | 任意类型 |
__rmatmul__ | @ | 左操作数不支持矩阵乘法时调用 | 任意类型 |
以__radd__
(★★★☆☆)为例展开说明
当左操作数不支持加法时调用,实现 other + self
的运算
- ✅ 函数签名
def __radd__(self, other):
# 返回反向加法结果
-
✅ 最佳实践
- 保持交换律:应确保
a + b == b + a
- 类型检查:验证 other 类型是否支持
- 回退机制:不支持时返回
NotImplemented
- 保持交换律:应确保
-
✅ 代码示例
class TextRepr:
def __init__(self, val):
self.val = val
def __add__(self, other):
return TextRepr(f"{self.val}+{other}")
def __radd__(self, other):
return TextRepr(f"{other}+{self.val}")
def __str__(self):
return self.val
t = TextRepr("world")
print("hello" + t) # hello+world
print(t + "hello") # world+hello
就地运算
就地运算方法实现增量赋值操作(如 +=),应当修改对象自身并返回结果(通常是 self)
魔术方法 | 运算符 | 功能描述 | 返回值要求 |
---|---|---|---|
__iadd__ | += | 就地加法 | 通常返回 self |
__isub__ | -= | 就地减法 | 通常返回 self |
__imul__ | *= | 就地乘法 | 通常返回 self |
__itruediv__ | /= | 就地真除法(返回浮点数) | 通常返回 self |
以 __iadd__
(★★★★☆):就地加法 为例展开说明
实现 +=
运算,直接修改对象并返回自身。
- ✅ 函数签名
def __iadd__(self, other):
# 修改self并返回
return self
-
✅ 最佳实践
- 原地修改:直接修改对象状态而非创建新对象
- 返回self:必须返回修改后的对象
- 类型检查:验证 other 类型是否支持
-
✅ 代码示例
class Buffer:
"""避免+=操作频繁创建临时对象"""
def __init__(self):
self.data = bytearray()
def __iadd__(self, other):
if isinstance(other, (bytes, bytearray)):
self.data.extend(other)
else:
self.data.extend(str(other).encode())
return self
buf = Buffer()
buf += b"hello"
buf += " world"
print(buf.data) # bytearray(b'hello world')
2.4 比较运算与哈希值
魔术方法 | 功能说明 |
---|---|
__eq__ | == |
__ne__ | != |
__lt__ | < |
__le__ | <= |
__gt__ | > |
__ge__ | >= |
__hash__ | 支持 hash(obj) ,用于 set/dict |
__bool__ | 控制 bool(obj) 的返回结果 |
以__eq__
为例展开说明:等于比较(★★★★★)
定义对象相等性比较 (==
) 的行为
- ✅ 函数签名
def __eq__(self, other):
# 返回布尔值
-
✅ 最佳实践
- 类型检查:建议先检查 other 类型
- 对称性:应保证
a == b
和b == a
结果一致 - 哈希一致性:如果实现
__eq__
,通常需要实现__hash__
代码示例1:自定义相等逻辑
class CaseInsensitiveStr:
def __init__(self, s):
self.s = s
def __eq__(self, other):
if isinstance(other, str):
return self.s.lower() == other.lower()
elif isinstance(other, CaseInsensitiveStr):
return self.s.lower() == other.s.lower()
return NotImplemented
def __hash__(self):
return hash(self.s.lower())
s1 = CaseInsensitiveStr("Hello")
s2 = CaseInsensitiveStr("hello")
print(s1 == s2) # True
print(s1 == "HELLO") # True
代码示例2:数据库记录比较
class DBRecord:
def __init__(self, id, **fields):
self.id = id
self.fields = fields
def __eq__(self, other):
if not isinstance(other, DBRecord):
return NotImplemented
return self.id == other.id
r1 = DBRecord(1, name="Alice")
r2 = DBRecord(1, name="Bob")
print(r1 == r2) # True (仅比较ID)
2.5 位运算
正向位运算
魔术方法 | 运算符 | 含义 |
---|---|---|
__and__ | & | 位与 |
__or__ | | | 位或 |
__xor__ | ^ | 位异或 |
__invert__ | ~ | 位取反(单目运算) |
__lshift__ | << | 左移 |
__rshift__ | >> | 右移 |
反向位运算
魔术方法 | 运算符 | 含义 |
---|---|---|
__rand__ | & | 右操作数调用的位与 |
__ror__ | | | 右操作数调用的位或 |
__rxor__ | ^ | 右操作数调用的位异或 |
__rlshift__ | << | 右操作数调用的左移 |
__rrshift__ | >> | 右操作数调用的右移 |
增强(就地)位运算
魔术方法 | 运算符 | 含义 |
---|---|---|
__iand__ | &= | 原地位与 |
__ior__ | |= | 原地位或 |
__ixor__ | ^= | 原地位异或 |
__ilshift__ | <<= | 原地左移 |
__irshift__ | >>= | 原地右移 |
2.6 集合/容器类型协议
这部分内容比较简单,就不重复啰嗦了,以__iter__
、__next__
作为示例展开说明
魔术方法 | 功能说明 |
---|---|
__len__ | len(obj) |
__getitem__ | obj[key] |
__setitem__ | obj[key] = value |
__delitem__ | del obj[key] |
__contains__ | item in obj |
__iter__ | for x in obj 时触发,返回迭代器 |
__next__ | 支持迭代器协议 next() |
__reversed__ | reversed(obj) |
__iter__
:可迭代协议入口(★★★★★)
让对象支持 for x in obj
,是构建自定义迭代容器的关键
- ✅ 函数签名
def __iter__(self):
return iterator
- ✅ 特性说明
特性 | 描述 |
---|---|
调用方式 | 当 for item in obj 被调用时触发 |
返回值类型 | 任意实现了 __next__ 的迭代器对象 |
与 __next__ 配合 | 通常返回 self (如果类同时定义了 __next__ )或新建的迭代器 |
-
✅ 最佳实践/推荐场景
-
自定义数据结构(如链表、树、图)
-
按需迭代大数据(如日志读取器)
-
延迟计算(如生成器封装)
-
__next__
:配合 __iter__
用于构造自定义迭代器(★★★☆☆)
配合 __iter__
,实现自定义的「惰性遍历逻辑」
- ✅ 函数签名
def __next__(self):
return next_value
- ✅ 特性说明
特性 | 描述 |
---|---|
触发方式 | next(obj) 或 for 循环中自动调用 |
异常控制 | 若结束迭代,需显式抛出 StopIteration 异常 |
可与状态维护配合 | 可记录当前位置、缓存、动态生成数据等 |
-
✅ 推荐场景:
-
封装状态流(如分页接口、读取器等)
-
实现迭代器协议
-
生成器的面向对象封装
-
综合示例:分页器
class PagedData:
def __init__(self, data, page_size=3):
self.data = data
self.page_size = page_size
def __iter__(self):
# 返回一个新的分页迭代器,每次 for 循环都从头开始
return PagedIterator(self.data, self.page_size)
class PagedIterator:
def __init__(self, data, page_size):
self.data = data
self.page_size = page_size
self.current = 0
def __next__(self):
if self.current >= len(self.data):
raise StopIteration
start = self.current
end = self.current + self.page_size
self.current = end
return self.data[start:end]
def __iter__(self):
return self
data = list(range(10)) # 模拟一组数据
pager = PagedData(data, page_size=4)
for page in pager:
print("Page:", page)
2.7 类型转换与反射相关
魔术方法 | 功能说明 |
---|---|
__int__ | int(obj) |
__float__ | float(obj) |
__complex__ | complex(obj) |
__index__ | 索引转换,如 bin(obj) |
__round__ | round(obj) |
__trunc__ | 截断整数 |
__floor__ | 向下取整 |
__ceil__ | 向上取整 |
__instancecheck__ | 支持 isinstance() 检查 |
__subclasscheck__ | 支持 issubclass() 检查 |
2.8 上下文管理
__enter__
和 __exit__
:上下文管理(★★★☆☆)
这对魔术方法共同实现了 Python 的上下文管理协议(Context Management Protocol),是 with
语句背后的核心机制。
方法 | 触发时机 | 返回值要求 | 异常处理 |
---|---|---|---|
__enter__ | 进入 with 块时调用 | 通常返回 self 或资源对象 | 异常会阻止 __exit__ 执行 |
__exit__ | 离开 with 块时调用(包括异常退出) | 应返回 None 或 False(True 表示已处理异常) | 接收异常信息参数 |
-
✅
__enter__
特点- 资源初始化:获取资源(如打开文件、连接数据库)
- 返回值绑定:
with ... as var
中的var
接收返回值 - 轻量操作:不应包含可能失败的操作
-
✅
__exit__
特点- 异常三元组:接收
(exc_type, exc_val, exc_tb)
参数 - 资源清理:必须保证资源释放(即使发生异常)
- 异常处理:返回 True 可抑制异常(慎用)
- 异常三元组:接收
-
✅ 最佳实践
- 资源管理(文件/网络/锁等)
- 事务处理(数据库事务)
- 状态管理(临时修改全局状态)
- 计时/日志(代码块执行时间统计)
代码示例1:基础文件操作
class FileHandler:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file # 绑定到 as 变量
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
# 不返回True,让异常正常传播
# 使用方式
with FileHandler('data.txt', 'w') as f:
f.write('Hello') # 自动确保文件关闭
代码示例2 :数据库事务
class DatabaseTransaction:
def __enter__(self):
self.conn = connect_to_db()
self.cursor = self.conn.cursor()
self.conn.begin()
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.conn.commit() # 无异常则提交
else:
self.conn.rollback() # 有异常则回滚
self.conn.close()
# 使用方式
with DatabaseTransaction() as cursor:
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
# 自动处理提交或回滚
代码示例3 :计时上下文
import time
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self # 返回自身以便访问结果
def __exit__(self, *args):
self.elapsed = time.perf_counter() - self.start
# 使用方式
with Timer() as t:
time.sleep(1)
print(f"耗时: {t.elapsed:.3f}秒")
代码示例4:异常处理
注意:__exit__(self, exc_type, exc_val, exc_tb)
三个参数的作用
参数名 | 类型 | 说明 |
---|---|---|
exc_type | type (异常类) | 异常的类型,例如 ZeroDivisionError 、ValueError 等。如果没有异常,它就是 None 。 |
exc_val | Exception (异常实例) | 异常对象本身,例如 ZeroDivisionError('division by zero') 。如果没有异常,也是 None 。 |
exc_tb | traceback (回溯对象) | 一个 traceback 对象,表示异常发生的位置和堆栈信息。可以用 traceback 模块进行分析。 |
class SafeExecutor:
def __enter__(self):
print("开始执行代码块")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f"捕获异常: {exc_type.__name__} - {exc_val}")
return True # 抑制异常传播
print("正常结束代码块")
# 使用方式
with SafeExecutor():
x = 1 / 0 # 被抑制的异常
print("程序继续运行")
代码示例5:与 contextlib
的配合使用
Python 提供了 contextlib
模块,可以更简洁地创建上下文管理器,尤其适用于轻量级逻辑:yield
前相当于 __enter__
,yield
后的内容相当于 __exit__
。
from contextlib import contextmanager
@contextmanager
def open_file(name, mode):
f = open(name, mode)
try:
yield f # 控制权交给 with 代码块
finally:
f.close()
# 使用方式
with open_file('data.txt', 'w') as f:
f.write('hello, contextlib')
2.9 调用对象
__call__
:使实例可调用(★★★★★)
允许类的实例像函数一样被调用,是实现可调用对象(callable object) 的关键方法
- ✅ 函数签名
def __call__(self, *args, **kwargs):
# 实现调用逻辑
return result
- ✅ 关键特性
特性 | 说明 |
---|---|
调用方式 | 实例后加括号 obj() 触发 |
返回值 | 可以返回任意类型 |
参数传递 | 支持位置参数和关键字参数 |
状态保持 | 实例可以维护调用间的状态 |
-
✅ 最佳实践/推荐场景
- 函数装饰器(最经典用法)
- 延迟计算/缓存(Memoization模式)
- 替代策略模式(比类+接口更Pythonic的实现)
- DSL构建(领域特定语言)
示例1:函数装饰器
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("✨ Before function call")
result = self.func(*args, **kwargs)
print("✨ After function call")
return result
@MyDecorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
print(type(greet)) # <class '__main__.MyDecorator'>
示例2:状态保持计算器
class PowerCalculator:
def __init__(self, exponent):
self.exponent = exponent
def __call__(self, base):
return base ** self.exponent
square = PowerCalculator(2)
cube = PowerCalculator(3)
print(square(5)) # 25
print(cube(3)) # 27
示例3:替代策略模式(更 Pythonic 的方式)
class TaxStrategy:
def __init__(self, rate):
self.rate = rate
def __call__(self, amount):
return amount * self.rate
standard = TaxStrategy(0.2)
reduced = TaxStrategy(0.1)
print(standard(100)) # 输出:20.0
print(reduced(100)) # 输出:10.0
示例4:配合functools.wraps装饰器保持元信息
import functools
class LogCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
def __call__(self, *args, **kwargs):
print(f"Calling {self.func.__name__} with {args}, {kwargs}")
return self.func(*args, **kwargs)
@LogCalls
def multiply(x, y):
"""Multiplies two numbers."""
return x * y
print(multiply(3, 4)) # 输出:12
print(multiply.__name__) # 输出:multiply
print(multiply.__doc__) # 输出:Multiplies two numbers.
示例5:实现记忆化缓存
class Memoized:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args not in self.cache:
self.cache[args] = self.func(*args)
return self.cache[args]
@Memoized
def expensive_calculation(n):
print(f"Computing for {n}...")
return n * n
print(expensive_calculation(4)) # 计算
print(expensive_calculation(4)) # 从缓存读取
2.10 属性访问控制
魔术方法 | 触发场景 | 典型应用场景 | 返回值要求 |
---|---|---|---|
__getattr__ | 当访问不存在的属性时调用 | 动态属性计算、属性别名、向后兼容 | 任意类型 |
__getattribute__ | 访问任何属性时调用(包括存在的属性) | 属性访问控制、属性访问日志记录 | 任意类型 |
__setattr__ | 当给属性赋值时调用 | 属性验证、自动类型转换、观察者模式 | None |
__delattr__ | 当删除属性时调用 | 资源清理、删除保护机制 | None |
__dir__ | 当调用 dir(obj) 时调用 | 动态属性展示、API 自省 | 返回属性名列表 |
__getattr__
:访问不存在的属性时调用(★★★★☆)
- ✅ 函数签名
def __getattr__(self, name):
# name 是访问的属性名
return value
- ✅ 关键特性
特性 | 说明 |
---|---|
触发条件 | 仅在 找不到属性时 才会触发(即 attribute error) |
返回值 | 可以返回任意值(甚至是函数或类),视你如何处理 |
典型用途 | 动态生成属性、提供属性别名、向后兼容、虚拟属性支持 |
- ✅ 推荐场景/最佳实践:
应用场景 | 描述 |
---|---|
动态计算属性 | 比如根据数据库或其他数据结构生成属性 |
属性别名兼容 | 老代码访问 userName ,但你现在用了 username |
代理/适配器模式 | 转发属性到另一个对象 |
懒加载/虚拟字段 | 在第一次访问时计算并缓存 |
代码示例1:属性别名兼容
class User:
def __init__(self, username):
self.username = username
def __getattr__(self, name):
if name == "userName":
return self.username # 老接口兼容
raise AttributeError(f"'User' object has no attribute '{name}'")
u = User("alice")
print(u.userName) # 输出:alice
代码示例2:动态生成属性(懒加载)
class LazyMath:
def __getattr__(self, name):
if name == "pi":
import math
return math.pi
raise AttributeError(f"No such attribute: {name}")
m = LazyMath()
print(m.pi) # 输出:3.141592653589793
代码示例3:将属性代理给另一个对象
class Config:
def __init__(self):
self.data = {
"host": "localhost",
"port": 3306
}
def __getattr__(self, name):
# 所有属性代理到 self.data
if name in self.data:
return self.data[name]
raise AttributeError(f"No config key: {name}")
cfg = Config()
print(cfg.host) # 输出:localhost
__getattribute__
:访问任意属性时调用(★★★★☆)
⚠️ ⚠️ 高风险方法,慎用。 每次访问属性都会调用,哪怕属性存在。推荐仅在需要全面拦截属性访问时使用。
- ✅ 函数签名
def __getattribute__(self, name):
return value
- ✅ 关键特性
特性 | 说明 |
---|
触发条件 | 所有属性访问都会触发,包括存在的属性 |
---|
返回值 | 必须返回属性的值(或抛出异常) |
---|
避免递归 | 使用 super().__getattribute__(name) 来访问真实属性,避免死循环 |
---|
- ✅ 推荐场景/最佳实践
应用场景 | 描述 |
---|---|
属性访问审计 | 记录访问日志或添加调试信息 |
统一属性权限控制 | 比如控制某些敏感属性只能内部访问 |
调试 / 日志 | 想知道外部程序访问了哪些属性 |
代码示例1:记录属性访问日志
class Logger:
def __init__(self, x):
self.x = x
def __getattribute__(self, name):
print(f"⚠️ Accessing: {name}")
return super().__getattribute__(name)
obj = Logger(42)
print(obj.x) # 输出访问日志 + 42
代码示例2:禁止访问某些属性
class SecureData:
def __init__(self):
self._secret = "top_secret"
self.public = "hello"
def __getattribute__(self, name):
if name == "_secret":
raise AttributeError("Access to _secret is forbidden!")
return super().__getattribute__(name)
s = SecureData()
print(s.public) # 输出 hello
print(s._secret) # 抛出异常
__setattr__
:设置属性时调用(★★★★★)
拦截所有属性赋值操作,常用于类型校验、自动转换、触发器机制等
- ✅ 函数签名
def __setattr__(self, name, value):
# 设置属性逻辑
- ✅ 关键特性
特性 | 说明 |
---|---|
触发条件 | obj.attr = value 会调用 |
避免递归 | 需要用 super().__setattr__(name, value) 否则会无限递归 |
验证/转换 | 可以拦截非法赋值、自动转换类型 |
- ✅ 推荐场景/最佳实践
应用场景 | 描述 |
---|---|
属性值验证 | 如 age 必须是正整数 |
自动类型转换 | 自动将字符串转 int、float |
数据绑定机制 | 类似观察者模式,值变更时触发更新 |
代码示例1:拦截并验证属性赋值
class Person:
def __setattr__(self, name, value):
if name == "age" and value < 0:
raise ValueError("年龄不能为负数")
super().__setattr__(name, value)
p = Person()
p.age = 18 # OK
p.age = -1 # 抛异常
代码示例2:自动类型转换
class Config:
def __setattr__(self, name, value):
if name == "port":
value = int(value) # 自动转换为整数
super().__setattr__(name, value)
c = Config()
c.port = "8080"
print(c.port + 1) # 输出:8081
__delattr__
:删除属性时调用(★★★☆☆)
允许对属性删除进行控制。比如保护某些属性不被删除,或触发清理操作
- ✅ 函数签名
def __delattr__(self, name):
# 删除属性逻辑
- ✅ 关键特性
特性 | 说明 |
---|---|
触发条件 | 使用 del obj.attr 时调用 |
用途 | 资源释放、删除日志、保护字段 |
代码示例1:禁止删除特定属性
class Safe:
def __init__(self):
self.password = "1234"
def __delattr__(self, name):
if name == "password":
raise AttributeError("不能删除密码!")
super().__delattr__(name)
s = Safe()
del s.password # 抛出异常
代码示例2:记录删除行为
class Tracked:
def __delattr__(self, name):
print(f"属性 {name} 被删除")
super().__delattr__(name)
t = Tracked()
t.x = 1
del t.x # 输出日志
__dir__
:自定义 dir(obj)
的返回值(★★★☆☆)
用于自定义对象有哪些属性,适合提升交互体验或构建 DSL(领域特定语言)
- ✅ 函数签名
def __dir__(self):
return ['foo', 'bar', 'baz']
- ✅ 推荐场景/用途
场景 | 描述 |
---|---|
动态 API 接口 | 比如连接外部服务后才暴露方法 |
交互模式友好 | 自定义属性提示,配合 IPython / Jupyter 更清晰 |
DSL 构建 | 域语言构造时更清晰的 API 入口 |
代码示例:返回自定义属性列表
class API:
def __dir__(self):
return ["login", "logout", "query"]
a = API()
print(dir(a)) # 输出自定义的 ['login', 'logout', 'query']
2.11 类的自定义行为控制
魔术方法 | 功能说明 |
---|---|
__class_getitem__ | 支持 MyClass[T] 泛型语法 |
__mro_entries__ | 控制类继承链(用于动态基类注入) |
__prepare__ | 元类中控制类体的命名空间 |
__init_subclass__ | 子类初始化时触发,常用于限制子类行为 |
__set_name__ | 描述符中用于获取被赋值属性的名称 |
__class_getitem__
:类支持下标访问(★★★☆☆)
允许类以 MyClass[T]
的形式支持“下标访问”,是实现自定义泛型行为和 DSL 的关键方法(Python 3.7+)
- ✅ 函数签名
def __class_getitem__(cls, key):
# 自定义下标语法行为
return something
- ✅ 关键特性
特性 | 说明 |
---|---|
触发方式 | 通过 MyClass[...] (类本身,不是实例)触发 |
返回值 | 可为任意对象,常用于生成类型、构建表达式等 |
常见用途 | 泛型支持、类型构造器、构建 DSL |
与实例无关 | 是类方法的一种变体,仅用于类本身 |
- ✅ 最佳实践/推荐场景
- 自定义泛型接口(如
Vector[int]
) - 类型系统框架/依赖注入容器
- 表达式构建器/查询构造器(如 ORM)
- 深度 DSL 设计(例如 PyTorch / TensorFlow 风格)
- 自定义泛型接口(如
代码示例1:Vector[T]
类型工厂
通过 Vector[int]
返回一个新类 ConcreteVector
,实现类型驱动的数据结构工厂
class Vector:
def __class_getitem__(cls, item):
class ConcreteVector(cls):
__type__ = item
def __repr__(self):
return f"Vector of {self.__type__}"
return ConcreteVector
IntVec = Vector[int]
StrVec = Vector[str]
print(IntVec()) # 输出:Vector of <class 'int'>
print(StrVec()) # 输出:Vector of <class 'str'>
代码示例2:服务容器注册器
类似于 FastAPI、Pydantic 等框架,使用 Container[ServiceType]
注册服务或自动注入依赖。
class ServiceContainer:
registry = {}
def __class_getitem__(cls, service_type):
return cls.registry.get(service_type)
def __class_setitem__(cls, service_type, instance):
cls.registry[service_type] = instance
# 注册服务
ServiceContainer[str] = "我是字符串服务"
ServiceContainer[int] = 123
print(ServiceContainer[str]) # 输出:我是字符串服务
代码示例3:ORM表达式构造
class Table:
def __class_getitem__(cls, field_name):
return Field(field_name)
class Field:
def __init__(self, name):
self.name = name
def __eq__(self, value):
return BinaryExpression(self, "=", value)
def __lt__(self, value):
return BinaryExpression(self, "<", value)
def __gt__(self, value):
return BinaryExpression(self, ">", value)
class BinaryExpression:
def __init__(self, left, operator, right):
self.left = left
self.operator = operator
self.right = right
def __repr__(self):
return f"{self.left.name} {self.operator} {repr(self.right)}"
# 示例用法
expr = Table["age"] > 18
print(expr) # 输出:age > 18
expr2 = Table["name"] == "Alice"
print(expr2) # 输出:name = 'Alice'
__mro_entries__
:控制类继承链的注入方式(★★★☆☆)
元类机制的一部分,用于动态修改类的基类(Python 3.7+)。
- ✅ 函数签名
def __mro_entries__(self, bases):
# 返回替代用的基类元组
return (NewBase,)
- ✅ 关键特性
特性 | 说明 |
---|---|
触发时机 | 类定义时,class C(D): 解析 D 时触发 |
返回值 | 替代原始基类的元组 |
典型用途 | 动态 mixin,框架 DSL,装饰器增强类行为 |
- ✅ 最佳实践/推荐场景
- 动态注入逻辑(如数据库模型基类自动注册)
- mock/mock.patch 中的 class 替换
- 高级 ORM / 多继承控制工具
代码示例:自动替换父类
class PluginBase:
pass
class PluginLoader:
def __mro_entries__(self, bases):
print("🔧 注入 PluginBase")
return (PluginBase,)
class MyPlugin(PluginLoader):
pass
print(issubclass(MyPlugin, PluginBase)) # True
__prepare__
:类体定义前初始化命名空间(★★★★☆)
元类中用于自定义类体的命名空间构造器,决定 class
中属性如何被收集。
- ✅ 函数签名
@classmethod
def __prepare__(metacls, name, bases):
return collections.OrderedDict() # 或任意 dict-like 对象
- ✅ 关键特性
特性 | 说明 |
---|---|
触发时机 | 类定义最初阶段,执行 class 体之前 |
返回值 | 返回作为类体命名空间的映射对象 |
可选性强 | 只有在定制元类时使用 |
配合元类使用 | 必须在 metaclass 中实现 |
- ✅ 最佳实践/推荐场景
- 保持属性定义顺序(Python < 3.6)
- 追踪定义顺序(字段注册)
- 实现 DSL/模型系统(如 Django ORM)
代码示例:记录类中属性定义顺序
from collections import OrderedDict
class Meta(type):
@classmethod
def __prepare__(metacls, name, bases):
return OrderedDict()
def __new__(cls, name, bases, namespace):
print(f"属性定义顺序:{list(namespace.keys())}")
return super().__new__(cls, name, bases, dict(namespace))
class MyClass(metaclass=Meta):
a = 1
b = 2
c = 3
__init_subclass__
:子类定义时自动钩子(★★★★☆)
父类定义后,每当有子类被定义时自动触发,常用于注册子类或统一配置。
- ✅ 函数签名
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 初始化子类行为
- ✅ 关键特性
特性 | 说明 |
---|---|
触发方式 | 定义子类 class Sub(Base) 时自动调用 |
无须元类 | 任何类都可定义,行为轻量 |
常用于注册子类 | 模式识别、插件系统等 |
- ✅ 最佳实践/推荐场景
- 插件注册器/工厂模式替代方案
- 约束子类结构(如必须定义某属性)
- 统一初始化配置逻辑
代码示例:自动注册子类
class Base:
registry = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.registry.append(cls)
class A(Base): pass
class B(Base): pass
print(Base.registry) # [<class '__main__.A'>, <class '__main__.B'>]
__set_name__
:描述符感知自己被赋予的属性名(★★★☆☆)
当描述符(实现了 __get__
/__set__
的对象)被绑定到类属性时自动调用。
- ✅ 函数签名
def __set_name__(self, owner, name):
self.name = name
- ✅ 关键特性
特性 | 说明 |
---|---|
仅在类定义阶段触发 | 在类体内赋值给属性后立即触发 |
不能用于实例赋值 | 只能在类属性中定义 |
用于描述符内部逻辑 | 常用于自动生成字段名、简化重复逻辑 |
- ✅ 最佳实践/推荐场景
- 实现字段绑定(如 dataclass/ORM 字段)
- 自动为描述符命名(避免手写 name)
- 实现字段校验器/可追踪字段等机制
示例:自动记录字段名
class Field:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
class Model:
id = Field()
name = Field()
m = Model()
m.id = 123
print(m.id) # 输出 123
2.12 内存优化与对象存储
方法 | 说明 | 示例/应用场景 |
---|---|---|
__slots__ | 限制实例属性 | __slots__ = ['x', 'y'] 禁止动态属性,节省内存40%~50% |
__sizeof__ | 自定义内存大小计算 | return object.__sizeof__() + sum(sys.getsizeof(v) for v in self.__dict__.values()) |
以__slots__
展开说明:限制实例属性(★★★★☆)
通过预定义属性列表来优化内存使用,禁止动态添加属性
- ✅ 函数签名
class ClassName:
__slots__ = ['attr1', 'attr2'] # 必须是字符串列表
- ✅ 关键特性
特性 | 说明 |
---|---|
内存优化 | 节省40%-50%内存(相比普通类) |
访问速度 | 属性访问速度提升约20% |
限制扩展 | 禁止动态添加未声明的属性 |
继承行为 | 子类不自动继承,需显式声明 |
-
✅ 最佳实践/推荐场景
- 需要创建大量实例的类(如ORM模型)
- 属性固定的数据对象
- 性能敏感场景下的内存优化
代码示例1:基础使用
class Point:
__slots__ = ['x', 'y'] # 只允许这两个属性
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.z = 3 # AttributeError
代码示例2:约束继承行为
class Base:
__slots__ = ['a']
class Child(Base):
__slots__ = ['b'] # 必须显式声明
# 现在实例只能有a和b属性
2.13 拷贝与序列化
方法 | 说明 | 示例/应用场景 |
---|---|---|
__copy__ | 控制浅拷贝 | return MyClass(copy.copy(self.data)) |
__deepcopy__ | 控制深拷贝 | new_obj = MyClass(); new_obj.data = deepcopy(self.data, memo) |
__reduce__ | 自定义pickle行为 | return (self.__class__, (self.essential_data,)) |
__copy__
:控制浅拷贝行为(★★★☆☆)
定义对象在使用copy.copy()
时的行为
- ✅ 函数签名
def __copy__(self):
# 返回浅拷贝对象
return new_object
- ✅ 关键特性
特性 | 说明 |
---|---|
对象复制 | 只复制对象本身,不复制嵌套对象 |
性能优势 | 比深拷贝更快,内存消耗更少 |
默认行为 | 未定义时返回新对象并复制__dict__ |
- ✅ 最佳实践
__copy__
本质上就是一种“可控的复制优化手段”,你可以跳过不该复制的部分,只复制必要的部分,从而实现更快、更轻量的对象构建 —— 在需要频繁复制的系统中(如图形、游戏、科学计算、缓存系统等)尤其重要。
代码示例:连接池不共用
import copy
class DBConfig:
def __init__(self, host, port, pool):
self.host = host
self.port = port
self.pool = pool
def __copy__(self):
# 创建一个新 pool 实例(或设置为 None),避免共用原 pool
new_pool = Pool(self.pool.id) # 模拟创建一个新的连接池
return DBConfig(self.host, self.port, new_pool)
class Pool:
def __init__(self, id):
self.id = id
pool = Pool(1)
config1 = DBConfig("localhost", 5432, pool)
config2 = copy.copy(config1)
print(config1.pool is config2.pool) # False ✅
代码示例2:与__slot__
组合使用避免dict优化性能
class Point:
__slots__ = ['x', 'y']
def __copy__(self):
return Point(self.x, self.y) # 避免dict操作
__deepcopy__
:控制深拷贝行为(★★★☆☆)
定义对象在使用copy.deepcopy()
时的行为
- ✅ 函数签名
def __deepcopy__(self, memo):
# memo用于处理循环引用
return new_object
- ✅ 关键特性
特性 | 说明 |
---|---|
递归复制 | 会递归复制所有嵌套对象 |
循环引用 | 通过memo字典处理循环引用 |
内存消耗 | 可能产生完整对象副本 |
代码示例:带缓存、指针的树状结构
import copy
class Node:
def __init__(self, value):
self.value = value
self.children = []
self._cached_str = None # 假设是一个懒加载的缓存字段
def __deepcopy__(self, memo):
# 如果已经复制过这个对象,就直接返回,防止无限递归
if id(self) in memo:
return memo[id(self)]
# 创建新对象,不拷贝缓存字段
copied = Node(self.value)
memo[id(self)] = copied # 标记为已复制
# 递归拷贝子节点
copied.children = [copy.deepcopy(child, memo) for child in self.children]
# 不复制 _cached_str(缓存字段通常在新对象中重新计算)
return copied
2.14 异步支持
方法 | 说明 | 示例/应用场景 |
---|---|---|
__aiter__ | 异步迭代入口 | async def __aiter__(self): return self |
__anext__ | 异步迭代实现 | async def __anext__(self): await asyncio.sleep(1); return data |
__await__ | 控制awaitable对象 | def __await__(self): yield from async_task() |
__aiter__
/ __anext__
:定义异步迭代器(★★★☆☆)
- ✅ 函数签名
def __aiter__(self) -> AsyncIterator:
return self
async def __anext__(self) -> Any:
# 异步返回下一个值,或抛出 StopAsyncIteration
...
- ✅ 关键特性
特性 | 说明 |
---|---|
迭代协议 | async for 会自动调用 __aiter__() 获取异步迭代器,再持续调用 __anext__() |
异步支持 | 每次获取元素前可以执行 await 操作(比如网络请求、磁盘读取) |
控制粒度更细 | 比同步迭代器更适合需要时间控制、异步资源的场景 |
异常结束 | 需要通过 raise StopAsyncIteration 来终止循环 |
与同步无冲突 | 不影响正常 for 循环,只适用于 async for |
-
✅ 最佳实践 / 推荐场景
- 异步读取大文件/行(如
aiofiles.open(...)
) - 消费异步消息队列(如
aiokafka
,aio_pika
) - 异步分页加载数据(如懒加载数据库数据)
- 自定义数据流生成器(如实时传感器模拟、爬虫页面流)
- 异步读取大文件/行(如
代码示例
import asyncio
class AsyncCounter:
def __init__(self, max_val):
self.current = 0
self.max_val = max_val
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.max_val:
raise StopAsyncIteration
await asyncio.sleep(0.5) # 模拟异步 I/O
self.current += 1
return self.current
async def main():
async for val in AsyncCounter(3):
print(val)
# 输出(每隔0.5秒):1 → 2 → 3
# asyncio.run(main())
__await__
:控制awaitable对象(★★★☆☆)
自定义可await对象的行为
- ✅ 函数签名
def __await__(self):
# 返回生成器
yield from coroutine
- ✅ 关键特性
特性 | 说明 |
---|---|
协议转换 | 将任意对象转为awaitable |
生成器要求 | 必须返回生成器对象 |
底层机制 | 被asyncio事件循环直接调用 |
代码示例
import asyncio
class FetchData:
def __init__(self, url):
self.url = url
def __await__(self):
return self._simulate_request().__await__()
async def _simulate_request(self):
await asyncio.sleep(1) # 模拟网络请求
return f"<data from {self.url}>"
async def main():
result = await FetchData("https://api.example.com/user")
print(result)
# 输出(1秒后):
# <data from https://api.example.com/user>
# asyncio.run(main())
2.15 类型系统集成
方法 | 说明 | 示例/应用场景 |
---|---|---|
__annotations__ | 类型声明存储 | {'name': str, 'age': int} 运行时获取类型注解 |
__get_validators__ | Pydantic验证器 | @classmethod def __get_validators__(cls): yield cls.validate |
__annotations__
:类型声明的存储容器(★★☆☆☆)
内置属性,而非方法。无需定义,由 Python 自动填充
- ✅ 关键特性
特性 | 说明 |
---|---|
自动填充 | 定义带类型注解的变量时,Python 会自动将其记录到该属性中 |
运行时可读 | 可用于反射、序列化、类型验证等场景 |
类与函数都支持 | 类属性、函数参数和返回值注解均可使用 __annotations__ 读取 |
未赋值字段也记录 | 即使没有给注解变量赋值,也会记录注解(例如:age: int ) |
-
✅ 最佳实践
- 动态读取字段定义(如 ORM、序列化器)
- 结合
get_type_hints()
获取完整注解(包括父类/延迟注解支持) - 用于自动化文档生成或表单构建
代码示例
class User:
name: str
age: int
print(User.__annotations__)
# 输出:{'name': <class 'str'>, 'age': <class 'int'>}
__get_validators__
:自定义验证器入口(★★★☆☆)
- ✅ 函数签名
@classmethod
def __get_validators__(cls) -> Generator[Callable, None, None]:
yield cls.validate
- ✅ 关键特性
特性 | 说明 |
---|---|
Pydantic特有 | 自定义类型用于 Pydantic 模型时,必须实现该方法 |
支持链式验证 | 可 yield 多个验证函数,依次执行 |
需为类方法 | 通常使用 @classmethod 装饰 |
返回生成器 | 每个 yield 的函数都应接受一个参数并返回处理后的值 |
-
✅ 最佳实践
-
自定义字段类型(如
Color
,DecimalRange
,Email
, 等) -
增加预处理/格式化逻辑(如自动小写、strip、格式校验)
-
提供更丰富的类型支持(如 IP 地址、Base64、ObjectId)
-
代码示例1:多验证器链式调用
class RGBColor:
@classmethod
def __get_validators__(cls):
yield cls.validate_type
yield cls.validate_range
@classmethod
def validate_type(cls, value):
if not isinstance(value, int):
raise TypeError("必须是整数")
return value
@classmethod
def validate_range(cls, value):
if not 0 <= value <= 255:
raise ValueError("必须在0-255范围内")
return cls(value)
class ImageConfig(BaseModel):
red: RGBColor
green: RGBColor
blue: RGBColor
# 调用示例
config = ImageConfig(red=255, green=128, blue=0) # 成功
invalid_config = ImageConfig(red=256, green=-1, blue="abc") # 抛出多个错误
示例代码2:带参数的高级验证
class RestrictedString:
def __init__(self, min_len: int = None, max_len: int = None):
self.min_len = min_len
self.max_len = max_len
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, value, **kwargs):
config = kwargs['config']
if config.min_len and len(value) < config.min_len:
raise ValueError(f"长度不能小于{config.min_len}")
if config.max_len and len(value) > config.max_len:
raise ValueError(f"长度不能大于{config.max_len}")
return cls(value)
class UserProfile(BaseModel):
username: RestrictedString = Field(..., min_len=4, max_len=20)
bio: RestrictedString = Field("", max_len=200)
3 经验
3.1 基本法则
✅ 法则1:克制使用,只在必要时使用
- 🚫 避免炫技,仅在需要自然交互或协议适配时使用(如数学运算、集合操作、类型转换)
- ⚠️ 例如:不要为了“炫技”实现
__add__
,除非你真的在处理一个可以加法运算的对象(如向量、矩阵、货币值等)
✅ 法则2:保持一致性
- 🔄 实现
__eq__
时,通常也应实现__hash__
,否则对象将无法用于set
、dict
作为 key - 🧩 对于集合相关类,推荐同时实现
__eq__
,__hash__
,__contains__
,__iter__
,以保证良好的集合语义
✅ 法则3:牢记默认行为,避免黑箱操作
- 📜 Python 有良好的默认回退机制:不定义
__str__
时,会自动调用__repr__
- 🔁 实现
__lt__
后,可用于sorted()
、min()
等排序函数,但如果只实现__lt__
而未实现__eq__
,可能导致排序后结果不一致 - 🧩 例如
__getattr__
应处理缺失字段,而非捕获所有属性访问,否则调试将变得异常困难
✅ 法则4:业务逻辑分离
- 🏗️ 魔术方法主要用于实现协议或底层钩子,不应承担业务逻辑职责
- 💡 反例:在
__init__
中直接写数据库调用或网络请求,这会破坏对象构造的纯粹性,影响可测性与稳定性
✅ 法则5:谨遵python之禅
"显式优于隐式" —— 魔术方法应当增强可读性而非制造魔法
✅ 法则6:类型系统一致性
- 🧩 当实现数值运算方法时,应返回
NotImplemented
而非抛出TypeError
,这是Python协议的标准做法 - 🔄 如果实现
__radd__
等反向方法,需保证a + b == b + a
的数学特性
3.2 不建议使用魔术方法的场景
场景 | 风险说明 | 替代方案 |
---|---|---|
关键业务逻辑 | 魔术方法调用隐式,调试困难 | 使用显式方法调用(如 .calculate() 而非 __add__ ) |
复杂状态管理 | 滥用 __getattribute__ / __setattr__ 易触发递归或隐藏错误 | 使用 @property 或描述符 |
资源管理 | __del__ 调用时机不确定,依赖垃圾回收机制 | 使用 with 和上下文管理器 (__enter__ / __exit__ ) |
性能敏感代码 | 魔术方法层层调用,性能不如直接操作 | 直接访问对象内部属性或使用内置方法 |
需要明确定义接口的 API | 隐式协议行为让接口难以发现和理解 | 使用标准函数或类方法并编写清晰文档 |
复杂继承结构 | 魔术方法易被覆盖,导致父类协议被破坏 | 优先使用组合(composition)而非继承 |
🚫 典型反例
class Database:
def __init__(self):
self.__query_count = 0
# ❌ 错误:递归调用 __getattribute__,导致死循环
def __getattribute__(self, name):
if name.startswith('query_'):
self.__query_count += 1 # 会再次触发 __getattribute__
return super().__getattribute__(name)
3.3 建议使用魔术方法的场景
场景 | 适用方法 | 应用示例 |
---|---|---|
构建领域特定语言(DSL) | __add__ 、__sub__ 、__mul__ 等 | 向量运算、物理单位库 |
策略/工厂模式替代 | __call__ 使对象可调用 | 配置项动态调用器 |
自定义容器 | __getitem__ , __len__ , __iter__ 等 | JSON-like 对象包装器 |
上下文管理器 | __enter__ , __exit__ | 自动释放资源 |
对象生命周期控制 | __new__ , __init__ , __del__ | 单例、池化对象 |
类型系统集成 | __instancecheck__ , __subclasscheck__ | 虚拟基类判定 |
调试信息增强 | __repr__ , __str__ | 提高可观测性 |
✅ 典型正例
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __repr__(self):
return f"Vector({self.x}, {self.y})"
3.4 性能优化与安全增强
避免递归陷阱
class Bad:
# ❌ 错误示范:无限递归
def __setattr__(self, name, value):
self.name = value # 会再次触发__setattr__
class Good:
# ✅ 正确做法
def __setattr__(self, name, value):
super().__setattr__(name, value)
缓存高频访问结果
class ExpensiveCompute:
def __init__(self):
self._cache = None
@property
def result(self):
if self._cache is None:
print("执行耗时计算...")
self._cache = sum(range(10**6))
return self._cache
敏感数据禁止深度拷贝
import copy
class SecureData:
def __deepcopy__(self, memo):
raise RuntimeError("此对象禁止深拷贝")
敏感数据访问管控
class SafeAccess:
def __getattribute__(self, name):
if name.startswith('_'):
raise AttributeError("访问私有属性被拒绝")
return super().__getattribute__(name)
super很多时候都比较可靠
总是调用 super()
:尤其在多重继承场景下,正确地调用 super()
能避免魔术方法链断裂
CPython优化
**字节码层面优化**:
```python
# 快:直接访问__dict__
def __getattr__(self, name):
return self.__dict__[name] # 字节码: LOAD_ATTR → LOAD_SUBSCR
# 慢:使用getattr()
def __getattr__(self, name):
return getattr(self.__dict__, name) # 涉及函数调用开销
线程安全
魔术方法本身不是线程安全的,在使用过程中如__iadd__
等就地运算中需自行加锁
import threading
class SafeCounter:
def __init__(self, value=0):
self.value = value
self._lock = threading.Lock()
def __iadd__(self, other):
if not isinstance(other, SafeCounter):
return NotImplemented
with self._lock:
self.value += other.value
return self
def __repr__(self):
return f"SafeCounter(value={self.value})"