9、魔法方法(上):__str__、__repr__、__len__、__eq__,让自定义类更“原生”
说实话,我刚开始学面向对象那会儿,最困惑的就是:
明明我写了一个类,创建了对象,怎么打印出来全是 <__main__.Book object at 0x...> 这种鬼东西?
这玩意儿谁看得懂啊?
后来我才明白,Python 留了一堆「钩子」给我们——魔法方法。
说白了,就是让自定义类表现得像内置类型一样自然。
你想想看,len('hello') 能返回 5,str(123) 能变成 '123',1 == 1 能返回 True。
这些能力,我们自己的类也能拥有。
今天我们就聊四个最常用的魔法方法:__str__、__repr__、__len__、__eq__。
掌握了它们,你的类会瞬间变得「原生」起来。
知识体系总览
先看一张图,帮你快速建立整体认知:
1. __str__ 与 __repr__:打印的「面子」与「里子」
这两个方法经常被放在一起讲,因为它们都负责「把对象变成字符串」。
但用途完全不同。
1.1 __str__:给用户看的
当你调用 print(obj) 或者 str(obj) 时,Python 会去找 __str__ 方法。
它应该返回一个简洁、可读、友好的字符串。
举个例子:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f"《{self.title}》—— {self.author}"
book = Book("Python编程:从入门到实践", "Eric Matthes", 460)
print(book) # 输出: 《Python编程:从入门到实践》—— Eric Matthes
你看,打印出来一目了然。
我个人的习惯是,__str__ 里只放用户最关心的信息,别啰嗦。
1.2 __repr__:给开发者看的
这个就有点意思了。
__repr__ 的目标是无歧义,最好能直接用来重建对象。
当你直接在交互式终端输入 obj 回车,或者用 repr(obj) 时,就会调用它。
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages})"
book = Book("Python编程", "Eric Matthes", 460)
print(repr(book)) # 输出: Book('Python编程', 'Eric Matthes', 460)
__repr__。因为当
__str__ 不存在时,Python 会退而求其次调用 __repr__。所以
__repr__ 是兜底方案,__str__ 是锦上添花。
__str__ 是给用户看的,__repr__ 是给程序员看的。前者追求「好看」,后者追求「精确」。
2. __len__:让你的对象也能被 len()
你有没有想过,为什么 len([1,2,3]) 能返回 3?
因为列表实现了 __len__ 方法。
我们的自定义类也可以。
class Playlist:
def __init__(self, name):
self.name = name
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __len__(self):
return len(self.songs)
def __str__(self):
return f"播放列表「{self.name}」共 {len(self)} 首歌曲"
my_list = Playlist("我的最爱")
my_list.add_song("晴天")
my_list.add_song("七里香")
my_list.add_song("夜曲")
print(len(my_list)) # 输出: 3
print(my_list) # 输出: 播放列表「我的最爱」共 3 首歌曲
嗯,这里要注意:__len__ 必须返回一个整数。
如果你返回了 None 或者字符串,Python 会直接报错。
我曾经在项目里踩过这个坑——返回了一个浮点数,结果 len() 直接炸了。
__len__ 的返回值必须是 int 类型,且 ≥ 0。如果你需要返回布尔值,请用
__bool__ 方法,别混用。
3. __eq__:定义「相等」的规则
默认情况下,两个对象比较的是内存地址。
也就是说,即使两个对象内容一模一样,== 也会返回 False。
这显然不合理。
class Student:
def __init__(self, student_id, name):
self.student_id = student_id
self.name = name
def __eq__(self, other):
if not isinstance(other, Student):
return False
return self.student_id == other.student_id
s1 = Student("001", "张三")
s2 = Student("001", "张三(重名)")
s3 = Student("002", "李四")
print(s1 == s2) # 输出: True(学号相同即视为同一人)
print(s1 == s3) # 输出: False
你看,我在这里只比较了 student_id。
因为在我的业务逻辑里,学号才是唯一标识。
名字可以改,但学号不会变。
实现了
__eq__ 之后,通常建议也实现 __hash__。否则你的对象不能放进
set 或者作为 dict 的键。简单做法:
__hash__ = None 表示对象不可哈希。
4. 综合示例:一个「原生感」满满的类
我们把今天学的四个方法整合到一个类里,看看效果:
class ShoppingCart:
def __init__(self, owner):
self.owner = owner
self.items = []
def add(self, item, price):
self.items.append((item, price))
def __len__(self):
return len(self.items)
def __str__(self):
return f"{self.owner}的购物车(共{len(self)}件商品)"
def __repr__(self):
return f"ShoppingCart('{self.owner}')"
def __eq__(self, other):
if not isinstance(other, ShoppingCart):
return False
return self.owner == other.owner and self.items == other.items
cart1 = ShoppingCart("小明")
cart1.add("苹果", 5.0)
cart1.add("牛奶", 12.5)
cart2 = ShoppingCart("小明")
cart2.add("苹果", 5.0)
cart2.add("牛奶", 12.5)
print(cart1) # 小明购物车(共2件商品)
print(repr(cart1)) # ShoppingCart('小明')
print(len(cart1)) # 2
print(cart1 == cart2) # True
你看,这个类现在用起来跟内置类型几乎没区别。
print 友好,len 可用,== 能正确比较。
这就是魔法方法的魅力——让你的类「融入」Python 生态。
5. 一张表总结
| 魔法方法 | 触发时机 | 返回值要求 | 典型用途 |
|---|---|---|---|
__str__ |
print()、str() |
字符串 | 用户友好的显示 |
__repr__ |
repr()、交互式终端 |
字符串(建议可重建对象) | 调试、日志 |
__len__ |
len() |
非负整数 | 容器类、集合类 |
__eq__ |
== |
布尔值 | 自定义相等性判断 |
写在最后
魔法方法不是什么玄学。
说白了,就是 Python 在背后帮你调用的「约定方法」。
你只要按照约定去实现,你的类就能获得「原生」般的能力。
我个人建议:
每写一个新类,先问自己三个问题——
1. 这个类需要被打印吗? → 实现 __str__
2. 这个类需要被调试吗? → 实现 __repr__
3. 这个类需要比较或计数吗? → 实现 __eq__ 和 __len__
养成这个习惯,你的代码会越来越「Pythonic」。