Python
双下划线方法
发布于 2026年7月26日
“特殊方法”(special methods),也常被称为“双下划线方法”“dunder methods”或“魔术方法”。
它们的核心作用是:让你定义的类能够配合 Python 的内置语法和函数使用。
例如:
str(obj) # 调用 obj.__str__()
len(obj) # 调用 obj.__len__()
obj[0] # 调用 obj.__getitem__(0)
obj1 + obj2 # 调用 obj1.__add__(obj2)
通常不要直接调用 obj.__str__(),而是使用对应的 Python 语法或函数。
1. 对象创建与初始化
__new__
负责“创建对象”,比 __init__ 更早执行。
class User:
def __new__(cls, *args, **kwargs):
print("创建对象")
return super().__new__(cls)
一般很少需要自己实现,主要用于:
- 不可变类型的定制
- 单例模式
- 控制对象创建过程
__init__
负责初始化已经创建好的对象。
class User:
def __init__(self, name, age):
self.name = name
self.age = age
使用:
user = User("小明", 18)
这行代码大致会依次调用:
User.__new__(User)
user.__init__("小明", 18)
严格来说,__init__ 不是构造函数,而是初始化方法。它不能返回其他值,必须返回 None。
继承时经常会使用:
class Student(User):
def __init__(self, name, age, school):
super().__init__(name, age)
self.school = school
2. 对象的字符串显示
__str__
定义给普通用户看的字符串,print() 和 str() 会使用它。
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return f"用户:{self.name}"
user = User("小明")
print(user) # 用户:小明
print(str(user)) # 用户:小明
在 Django 模型中,__str__ 经常用于定义后台管理页面如何显示对象。
__repr__
定义给开发者调试时看的字符串,通常应该包含更多信息。
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"User(name={self.name!r}, age={self.age!r})"
user = User("小明", 18)
print(repr(user))
# User(name='小明', age=18)
两者可以同时存在:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name
def __repr__(self):
return f"User(name={self.name!r}, age={self.age!r})"
print(user) # 使用 __str__
print(repr(user)) # 使用 __repr__
3. 长度和真假判断
| 特殊方法 | 对应操作 | 作用 |
|---|---|---|
__len__ |
len(obj) |
返回对象的长度 |
__bool__ |
bool(obj)、if obj |
判断对象真假 |
__len__
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
team = Team(["小明", "小红"])
print(len(team)) # 2
__len__ 必须返回非负整数。
__bool__
class Account:
def __init__(self, balance):
self.balance = balance
def __bool__(self):
return self.balance > 0
account = Account(100)
if account:
print("账户有余额")
如果没有定义 __bool__,Python 可能会使用 __len__ 判断:长度为 0 表示假,其他长度表示真。
4. 比较对象
| 特殊方法 | 对应操作 |
|---|---|
__eq__ |
a == b |
__ne__ |
a != b |
__lt__ |
a < b |
__le__ |
a <= b |
__gt__ |
a > b |
__ge__ |
a >= b |
例如,根据年龄比较用户:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
if not isinstance(other, User):
return NotImplemented
return self.name == other.name and self.age == other.age
def __lt__(self, other):
if not isinstance(other, User):
return NotImplemented
return self.age < other.age
user1 = User("小明", 18)
user2 = User("小红", 20)
print(user1 == user2) # False
print(user1 < user2) # True
NotImplemented 表示“不支持与这种类型比较”,它和 NotImplementedError 不是一回事。
5. 数学运算
| 特殊方法 | 对应操作 |
|---|---|
__add__ |
a + b |
__sub__ |
a - b |
__mul__ |
a * b |
__truediv__ |
a / b |
__floordiv__ |
a // b |
__mod__ |
a % b |
__pow__ |
a ** b |
__neg__ |
-a |
例如,实现两个坐标相加:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Point):
return NotImplemented
return Point(
self.x + other.x,
self.y + other.y,
)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2) # (4, 6)
还有反向运算:
__radd__
__rsub__
__rmul__
例如 10 + obj 无法完成时,Python可能尝试调用 obj.__radd__(10)。
6. 下标、切片和成员判断
| 特殊方法 | 对应操作 |
|---|---|
__getitem__ |
obj[index] |
__setitem__ |
obj[index] = value |
__delitem__ |
del obj[index] |
__contains__ |
value in obj |
例如:
class Students:
def __init__(self):
self.data = ["小明", "小红", "小刚"]
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __contains__(self, value):
return value in self.data
students = Students()
print(students[0]) # 小明
students[0] = "张三"
print(students[0]) # 张三
print("小红" in students) # True
__getitem__ 也可以接收切片:
students[0:2]
此时传进去的是一个 slice 对象。
7. 循环和迭代
__iter__
让对象可以被 for 循环遍历。
class Students:
def __init__(self, names):
self.names = names
def __iter__(self):
return iter(self.names)
students = Students(["小明", "小红"])
for name in students:
print(name)
__next__
定义每次迭代返回什么。当没有更多数据时,需要抛出 StopIteration。
class Counter:
def __init__(self, maximum):
self.current = 1
self.maximum = maximum
def __iter__(self):
return self
def __next__(self):
if self.current > self.maximum:
raise StopIteration
value = self.current
self.current += 1
return value
for number in Counter(3):
print(number)
输出:
1
2
3
8. 让对象像函数一样调用
__call__
定义后,对象后面可以直接写括号:
class Adder:
def __init__(self, amount):
self.amount = amount
def __call__(self, number):
return number + self.amount
add_five = Adder(5)
print(add_five(10)) # 15
这里的 add_five(10) 实际会调用:
add_five.__call__(10)
这类对象也叫“可调用对象”。
9. with 上下文管理
| 特殊方法 | 作用 |
|---|---|
__enter__ |
进入 with 代码块时执行 |
__exit__ |
离开 with 代码块时执行 |
class Connection:
def __enter__(self):
print("打开连接")
return self
def send(self, message):
print(f"发送:{message}")
def __exit__(self, exc_type, exc_value, traceback):
print("关闭连接")
with Connection() as connection:
connection.send("你好")
输出:
打开连接
发送:你好
关闭连接
即使 with 中发生异常,__exit__ 通常仍会执行,所以它很适合关闭文件、连接或释放资源。
10. 属性访问
| 特殊方法 | 触发条件 |
|---|---|
__getattr__ |
正常找不到属性时 |
__getattribute__ |
每一次读取属性时 |
__setattr__ |
每一次设置属性时 |
__delattr__ |
删除属性时 |
__getattr__
class User:
def __init__(self, name):
self.name = name
def __getattr__(self, attribute):
return f"不存在的属性:{attribute}"
user = User("小明")
print(user.name) # 小明
print(user.age) # 不存在的属性:age
__getattribute__ 和 __setattr__ 会拦截几乎所有属性操作,使用不当容易造成无限递归,因此初学时应谨慎使用。
11. 哈希值
__hash__
决定对象能否作为字典的键,或者放入集合中。
class User:
def __init__(self, user_id):
self.user_id = user_id
def __eq__(self, other):
if not isinstance(other, User):
return NotImplemented
return self.user_id == other.user_id
def __hash__(self):
return hash(self.user_id)
user = User(1001)
users = {user}
data = {user: "用户资料"}
注意:如果会影响哈希值的属性可以被修改,就不适合把该对象放进集合或作为字典键。
12. 异步操作
异步编程中也有对应的特殊方法:
| 特殊方法 | 对应语法 |
|---|---|
__await__ |
await obj |
__aiter__ |
async for |
__anext__ |
async for 的下一项 |
__aenter__ |
async with 进入 |
__aexit__ |
async with 退出 |
这些通常在异步框架、网络请求和数据库连接中使用。
一个综合例子
class ShoppingCart:
def __init__(self):
self.products = []
def __str__(self):
return f"购物车中有 {len(self.products)} 件商品"
def __repr__(self):
return f"ShoppingCart(products={self.products!r})"
def __len__(self):
return len(self.products)
def __bool__(self):
return bool(self.products)
def __getitem__(self, index):
return self.products[index]
def __contains__(self, product):
return product in self.products
def __iter__(self):
return iter(self.products)
def __add__(self, product):
new_cart = ShoppingCart()
new_cart.products = self.products + [product]
return new_cart
使用:
cart = ShoppingCart()
cart = cart + "苹果"
cart = cart + "牛奶"
print(cart) # 购物车中有 2 件商品
print(len(cart)) # 2
print(cart[0]) # 苹果
print("牛奶" in cart) # True
if cart:
print("购物车不是空的")
for product in cart:
print(product)
可以把特殊方法理解为一套“协议”:
想让对象支持 print() → 实现 __str__
想让对象支持 len() → 实现 __len__
想让对象支持 obj[0] → 实现 __getitem__
想让对象支持 for → 实现 __iter__
想让对象支持 + → 实现 __add__
想让对象支持 with → 实现 __enter__ 和 __exit__
不需要把它们全部背下来。实际开发时,先确定希望对象支持哪一种 Python 操作,再实现对应的特殊方法即可。