什么是 dunder

双下划线(double underscore)方法——__xxx__。Python 用它们让你的类能用 +==len()for 等内置语法。

str vs repr

class Player:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def __str__(self):       # 给人看的(print 用)
        return f"{self.name}(HP={self.hp})"

    def __repr__(self):      # 给开发者看的(控制台 / log 用)
        return f"Player(name={self.name!r}, hp={self.hp})"


p = Player("WadeLy", 100)
print(p)           # WadeLy(HP=100)
print(repr(p))     # Player(name='WadeLy', hp=100)
[p, p]             # [Player(name='WadeLy', hp=100), ...]   list 用 repr

只写一个的话写 __repr__——str 不存在时会回退到它。

eq + hash

让对象可以用 == 比较,也能放进 set / dict 当 key:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)

    def __hash__(self):
        return hash((self.x, self.y))


Point(1, 2) == Point(1, 2)          # True
{Point(1, 2), Point(1, 2)}          # 集合里只有一个

⚠️ 重写 __eq__ 必须重写 __hash__,否则对象会变成不可哈希的。

len + getitem:让对象长得像列表

class Deck:
    def __init__(self):
        self.cards = ["A", "2", "3", "...K"]

    def __len__(self):
        return len(self.cards)

    def __getitem__(self, idx):
        return self.cards[idx]


d = Deck()
len(d)       # 4
d[0]         # 'A'
d[1:3]       # 切片也能用!(前提是底层 list 支持)
for c in d:  # for 也能用!(有 __getitem__ 就够)
    print(c)

iter + next

讲过了——让对象成为迭代器。

call:让对象像函数

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return x + self.n

add5 = Adder(5)
add5(10)        # 15  像调函数一样调它

类装饰器、缓存器、配置器常用这个。

enter + exit

讲过了——让对象支持 with

数学运算符:add / sub / ...

class Vec:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vec(self.x + other.x, self.y + other.y)

    def __mul__(self, k):
        return Vec(self.x * k, self.y * k)

    def __repr__(self):
        return f"Vec({self.x}, {self.y})"


a = Vec(1, 2)
b = Vec(3, 4)
print(a + b)           # Vec(4, 6)
print(a * 3)           # Vec(3, 6)
符号 方法
+ __add__
- __sub__
* __mul__
/ __truediv__
// __floordiv__
% __mod__
** __pow__
< __lt__
> __gt__
== __eq__

全部魔术方法的速查

dir(int) / dir(list) 看内置类型实现了哪些 dunder——直接抄它们的方法名。

别滥用

写"Vec + Vec" 让代码更清晰——好。 写 "User > User" 比较年龄——别。读代码的人猜不到 > 的语义。

下一篇讲 property 与描述符——getter/setter 的优雅写法。