← 返回题目列表

Python 魔术方法有什么用?常见的 __str__、__repr__、__eq__ 怎么理解?

高频 中等 第 8 / 21 题 更新于 2026/07/27
魔术方法__str____repr____eq__运算符重载

简化版

魔术方法是以双下划线开头和结尾的特殊方法,用来让自定义对象接入 Python 的内置语法和协议。比如 __str__ 控制面向用户的字符串展示,__repr__ 控制调试表示,__eq__ 控制 == 比较逻辑。

详细版

示例:

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

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

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

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

使用:

p = Point(1, 2)
print(str(p))   # (1, 2)
print(repr(p))  # Point(1, 2)
print(p == Point(1, 2))  # True

常见魔术方法:

方法作用
__init__初始化对象
__str__str(obj)print(obj)
__repr__repr(obj)、调试输出
__eq__obj == other
__len__len(obj)
__iter__让对象可迭代
__getitem__支持 obj[key]
__enter__ / __exit__支持 with 上下文管理

完整版教学

一、魔术方法不是让你手动调用的

魔术方法通常不是这样用:

obj.__str__()

而是由 Python 的语法或内置函数自动调用:

str(obj)
print(obj)
len(obj)
obj == other

它们让自定义对象能融入 Python 语言本身。

比如实现 __len__

class Bag:
    def __init__(self, items):
        self.items = items

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

bag = Bag([1, 2, 3])
print(len(bag))  # 3

二、strrepr 的区别

__str__ 面向用户,可读即可:

def __str__(self):
    return f"用户:{self.name}"

__repr__ 面向开发者和调试,最好尽量准确:

def __repr__(self):
    return f"User(name={self.name!r})"

如果只实现 __repr__ 没有实现 __str__str(obj) 通常会退回使用 __repr__

工程里建议至少给重要领域对象实现 __repr__,排查日志时很有用。

三、eq 的正确写法

比较两个对象是否“值相等”时,可以实现 __eq__

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

为什么返回 NotImplemented

因为这表示当前类不知道如何和对方比较,让 Python 尝试对方类型的比较逻辑,或者最终判断为不相等。

不建议直接返回 False 处理所有陌生类型,因为可能破坏反向比较机会。

四、eqhash 的关系

如果对象实现了自定义 __eq__,Python 可能会把它变成不可哈希,避免对象放进集合或作为字典 key 后行为不一致。

因为哈希对象必须满足:

如果 a == b,那么 hash(a) 必须等于 hash(b)

如果对象可变,却又能作为 dict key,修改后哈希值变化会导致字典结构出问题。

所以实现值对象时,要么让对象不可变并提供稳定 __hash__,要么不要让它可哈希。

五、运算符重载

魔术方法还能支持运算符:

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

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

调用:

v3 = v1 + v2

背后会调用:

v1.__add__(v2)

运算符重载要符合直觉。不要让 + 做保存数据库、发请求这类副作用很强的事情。

六、协议式编程

很多魔术方法对应 Python 协议:

  • 实现 __iter__,对象可迭代;
  • 实现 __enter__ / __exit__,对象可用于 with
  • 实现 __getitem__,对象可下标访问;
  • 实现 __call__,对象可像函数一样调用。

这也是 Python 面向对象的特色:你不一定要继承某个接口,只要实现约定方法,就能接入对应语法。

七、常见误区与追问

心法:魔术方法是在实现 Python 协议,不是在堆砌特殊名字。你写 len(obj)obj + otherfor x in obj,解释器会去找对应协议方法。

  • 误区:魔术方法应该直接手动调用。 通常应让语法或内置函数触发它们,例如用 len(obj) 触发 __len__,用 str(obj) 触发 __str__,这样更符合协议语义。
  • 追问:__repr____str__ 面试怎么区分? __str__ 面向用户友好展示,__repr__ 面向开发者调试,理想情况下 repr(obj) 应尽量包含可识别对象状态的信息。
  • 误区:实现 __eq__ 后对象还能自然作为 dict key。 如果类重写了 __eq__ 但没有合适的 __hash__,Python 会把 __hash__ 设为 None,避免可哈希契约被破坏。
  • 追问:二元运算符方法返回 NotImplemented 有什么用? 它告诉 Python 当前类型不处理这个操作,解释器可以尝试右操作数的反向方法,而不是立刻抛错。
  • 误区:魔术方法越多,类就越 Pythonic。 只有对象确实符合某个协议时才实现对应方法;为了炫技重载不自然的运算符,会让代码更难读。
  • 追问:可迭代对象最少需要实现什么? 常见方式是实现 __iter__ 返回迭代器;迭代器还要实现 __next__,耗尽时抛 StopIteration

八、加强记忆

魔术方法是 Python 给对象接入语言语法的钩子。__str__ 给用户看,__repr__ 给开发者看,__eq__ 定义值相等。写魔术方法要符合 Python 语义预期,别为了炫技把对象行为设计得反直觉。