浏览知识库目录

Python

双下划线方法

“特殊方法”(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 操作,再实现对应的特殊方法即可。