首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >5个超实用Python Dunder方法!掌握类的核心特性

5个超实用Python Dunder方法!掌握类的核心特性

原创
作者头像
小白的大数据之旅
发布2025-09-25 10:36:05
发布2025-09-25 10:36:05
7900
代码可运行
举报
运行总次数:0
代码可运行

5 个超实用 Python Dunder 方法!掌握类的核心特性

咱先唠唠啥是 Dunder 方法?其实就是 Python 里那些用双下划线__包裹的方法(比如__eq____str__),翻译过来叫 “特殊方法”。这些方法能让你的类拥有 “超能力”—— 比如让对象能比较相等、能自定义打印格式、甚至能用下标访问(像列表一样)。

今天咱就用一个Fruit类当例子,把 5 个最常用的 Dunder 方法讲明白。先定义一个基础的Fruit类,后面每个方法都基于它扩展:

代码语言:python
代码运行次数:0
运行
复制
# 基础Fruit类:包含名称、颜色、数量三个属性

class Fruit:

   def __init__(self, name, color, quantity):

       self.name = name  # 水果名,比如"苹果"

       self.color = color  # 颜色,比如"红色"

       self.quantity = quantity  # 数量,比如5

1. __eq__:让对象能比较 “相等”

咱先想个问题:如果创建两个 “看起来一样” 的水果对象,用==比较会咋样?

1.1 默认情况的坑

先跑段代码看看默认行为:

代码语言:python
代码运行次数:0
运行
复制
# 创建两个"相同"的苹果

apple1 = Fruit("苹果", "红色", 5)

apple2 = Fruit("苹果", "红色", 5)

# 用==比较

print(apple1 == apple2)  # 输出:False

为啥会是False?因为 Python 默认的==比较的是 “对象的内存地址”(相当于is运算符),哪怕两个对象的属性完全一样,只要是不同的实例,就判定为不相等。这显然不符合咱的实际需求 —— 咱想要的是 “属性相同就相等”。

1.2 实现__eq__方法

要解决这个问题,就得重写__eq__方法,告诉 Python “怎么判断两个 Fruit 相等”。咱规定:只要namecolorquantity都相同,就认为是相等的。

修改后的Fruit类(新增__eq__):

代码语言:python
代码运行次数:0
运行
复制
class Fruit:

   def __init__(self, name, color, quantity):

       self.name = name

       self.color = color

       self.quantity = quantity

   # 新增__eq__方法:比较两个Fruit的核心属性

   def __eq__(self, other):

       # 先判断other是不是Fruit类型,避免报错

       if not isinstance(other, Fruit):

           return False

       # 比较三个核心属性,全相同则返回True

       return (self.name == other.name

               and self.color == other.color

               and self.quantity == other.quantity)

1.3 测试效果

再跑之前的代码,结果就对了:

代码语言:python
代码运行次数:0
运行
复制
apple1 = Fruit("苹果", "红色", 5)

apple2 = Fruit("苹果", "红色", 5)

apple3 = Fruit("苹果", "绿色", 5)  # 颜色不同

print(apple1 == apple2)  # 输出:True(属性全相同)

print(apple1 == apple3)  # 输出:False(颜色不同)

print(apple1 == "苹果")  # 输出:False(other不是Fruit类型)

1.4 常见问题 & 注意事项

  • 坑 1:没判断 other 类型:如果直接写return self.name == other.name...,当other不是 Fruit(比如字符串)时,会报AttributeError(找不到 name 属性)。所以必须先加isinstance(other, Fruit)判断。
  • 坑 2:只比较部分属性:比如只比较name,那 “红色 5 个苹果” 和 “绿色 10 个苹果” 会被判相等,这显然不对。要根据业务场景选全量或核心属性比较。

2. __format__:自定义格式化输出

咱平时用format()函数或 f-string 格式化输出时,默认打印对象会是"<__main__.Fruit object at 0x0000023456789ABC>",特别不直观。__format__方法能让咱自定义格式化规则。

2.1 实现__format__方法

咱给Fruit加个__format__,支持 3 种格式化规则(用表格列清楚):

格式化符

作用

示例输出

s

输出完整信息

苹果(红色,5 个)

c

只输出颜色和名称

红色苹果

q

只输出名称和数量

苹果:5 个

修改后的Fruit类(新增__format__):

代码语言:python
代码运行次数:0
运行
复制
class Fruit:

   def __init__(self, name, color, quantity):

       self.name = name

       self.color = color

       self.quantity = quantity

   def __eq__(self, other):

       if not isinstance(other, Fruit):

           return False

       return (self.name == other.name

               and self.color == other.color

               and self.quantity == other.quantity)

   # 新增__format__方法:自定义格式化逻辑

   def __format__(self, format_spec):

       # 根据format_spec(格式化符)返回不同结果

       if format_spec == "s":

           return f"{self.name}({self.color},{self.quantity}个)"

       elif format_spec == "c":

           return f"{self.color}{self.name}"

       elif format_spec == "q":

           return f"{self.name}:{self.quantity}个"

       # 默认情况(没传格式化符),返回完整信息

       else:

           return f"Fruit(name={self.name}, color={self.color}, quantity={self.quantity})"

2.2 测试效果

format()或 f-string 测试,结果完全符合预期:

代码语言:python
代码运行次数:0
运行
复制
apple = Fruit("苹果", "红色", 5)

# 1. 用format()函数

print(format(apple, "s"))  # 输出:苹果(红色,5个)

print(format(apple, "c"))  # 输出:红色苹果

print(format(apple, "q"))  # 输出:苹果:5个

print(format(apple))       # 输出:Fruit(name=苹果, color=红色, quantity=5)(默认)

# 2. 用f-string(更常用)

print(f"我买了一个{apple:s}")  # 输出:我买了一个苹果(红色,5个)

print(f"这个{apple:c}真甜")    # 输出:这个红色苹果真甜

2.3 常见问题 & 注意事项

  • 坑 1:格式化符没处理:如果传了没定义的格式化符(比如format(apple, "x")),会走默认逻辑。如果没写默认分支,会返回None,打印出来是None,所以一定要加默认处理。
  • 坑 2:返回非字符串__format__必须返回字符串,要是不小心返回了数字(比如return self.quantity),会报TypeError,这点要注意。

3. __or__:实现对象的 “合并” 操作

__or__方法对应 Python 里的|运算符(按位或),咱可以用它实现 “合并两个水果对象” 的逻辑。比如把 “红色 3 个苹果” 和 “绿色 2 个苹果” 合并成 “红绿色 5 个苹果”。

3.1 实现__or__方法

咱定义合并规则:

  1. name必须相同(不同水果不能合并,比如苹果和香蕉不能合并);
  2. color合并成集合(去重,比如 “红色”+“绿色”→“红绿色”);
  3. quantity相加(3+2=5);
  4. 合并后返回新的 Fruit 对象(不修改原来的两个对象)。

修改后的Fruit类(新增__or__):

代码语言:python
代码运行次数:0
运行
复制
class Fruit:

   def __init__(self, name, color, quantity):

       self.name = name

       self.color = color

       self.quantity = quantity

   def __eq__(self, other):

       if not isinstance(other, Fruit):

           return False

       return (self.name == other.name

               and self.color == other.color

               and self.quantity == other.quantity)

   def __format__(self, format_spec):

       if format_spec == "s":

           return f"{self.name}({self.color},{self.quantity}个)"

       elif format_spec == "c":

           return f"{self.color}{self.name}"

       elif format_spec == "q":

           return f"{self.name}:{self.quantity}个"

       else:

           return f"Fruit(name={self.name}, color={self.color}, quantity={self.quantity})"

   # 新增__or__方法:实现水果合并(对应|运算符)

   def __or__(self, other):

       # 先判断两个水果的name是否相同,不同则报错

       if self.name != other.name:

           raise ValueError(f"不能合并不同种类的水果:{self.name} 和 {other.name}")

       # 合并颜色:用集合去重,再转成字符串(比如{"红色","绿色"}→"红绿色")

       merged_color = "".join(set([self.color, other.color]))

       # 合并数量:直接相加

       merged_quantity = self.quantity + other.quantity

       # 返回新的Fruit对象

       return Fruit(self.name, merged_color, merged_quantity)

3.2 测试效果

|运算符合并水果,看看效果:

代码语言:python
代码运行次数:0
运行
复制
# 创建两个苹果(同种类,不同颜色和数量)

red_apple = Fruit("苹果", "红色", 3)

green_apple = Fruit("苹果", "绿色", 2)

# 合并两个苹果

merged_apple = red_apple | green_apple

print(format(merged_apple, "s"))  # 输出:苹果(红绿色,5个)(颜色合并,数量相加)

# 尝试合并苹果和香蕉(不同种类)

banana = Fruit("香蕉", "黄色", 4)

try:

   red_apple | banana

except ValueError as e:

   print(e)  # 输出:不能合并不同种类的水果:苹果 和 香蕉(符合预期)

3.3 常见问题 & 注意事项

  • 坑 1:修改原对象__or__应该返回新对象,而不是修改selfother。如果写成self.quantity += other.quantity,会导致原对象被修改,后续再用原对象就出问题了。
  • 坑 2:没处理类型错误:如果用red_apple | 123(other 不是 Fruit),会报AttributeError(找不到 name 属性)。可以加if not isinstance(other, Fruit): raise TypeError(...)优化。

4. __str__ & __repr__:控制对象的 “字符串表示”

这俩方法经常被搞混,都是用来显示对象的字符串形式,但用途完全不同。咱先拿表格对比清楚,再写代码。

4.1 两者的核心区别

特性

__str__

__repr__

作用

给 “用户” 看的友好输出

给 “开发者” 看的详细输出

使用场景

print(对象)str(对象)

repr(对象)、交互式环境直接输对象

核心要求

简洁、易懂

详细、准确(最好能通过输出重建对象)

默认行为

__repr__(打印内存地址)

打印 “<类名 object at 内存地址>”

4.2 实现__str____repr__

咱给Fruit加这两个方法,遵循上面的区别:

  • __str__:友好显示,比如 “红色苹果(5 个)”;
  • __repr__:详细到能重建对象,比如Fruit("苹果", "红色", 5)

修改后的Fruit类(新增__str____repr__):

代码语言:python
代码运行次数:0
运行
复制
class Fruit:

   def __init__(self, name, color, quantity):

       self.name = name

       self.color = color

       self.quantity = quantity

   def __eq__(self, other):

       if not isinstance(other, Fruit):

           return False

       return (self.name == other.name

               and self.color == other.color

               and self.quantity == other.quantity)

   def __format__(self, format_spec):

       if format_spec == "s":

           return f"{self.name}({self.color},{self.quantity}个)"

       elif format_spec == "c":

           return f"{self.color}{self.name}"

       elif format_spec == "q":

           return f"{self.name}:{self.quantity}个"

       else:

           return f"Fruit(name={self.name}, color={self.color}, quantity={self.quantity})"

   def __or__(self, other):

       if not isinstance(other, Fruit):

           raise TypeError("只能合并Fruit类型的对象")

       if self.name != other.name:

           raise ValueError(f"不能合并不同种类的水果:{self.name} 和 {other.name}")

       merged_color = "".join(set([self.color, other.color]))

       merged_quantity = self.quantity + other.quantity

       return Fruit(self.name, merged_color, merged_quantity)

   # 新增__str__:给用户看的友好输出

   def __str__(self):

       return f"{self.color}{self.name}({self.quantity}个)"

   # 新增__repr__:给开发者看的详细输出(可重建对象)

   def __repr__(self):

       return f'Fruit(name="{self.name}", color="{self.color}", quantity={self.quantity})'

4.3 测试效果

分别测试print()(调用__str__)和repr()(调用__repr__):

代码语言:python
代码运行次数:0
运行
复制
apple = Fruit("苹果", "红色", 5)

# 1. 测试__str__(用户视角)

print(apple)          # 输出:红色苹果(5个)(调用__str__)

print(str(apple))     # 输出:红色苹果(5个)(直接调用__str__)

print(f"我有一个{apple}")  # 输出:我有一个红色苹果(5个)(f-string调用__str__)

# 2. 测试__repr__(开发者视角)

print(repr(apple))    # 输出:Fruit(name="苹果", color="红色", quantity=5)(调用__repr__)

# 在交互式环境里直接输apple,也会显示__repr__的结果

# >>> apple

# Fruit(name="苹果", color="红色", quantity=5)

4.4 常见问题 & 注意事项

  • 坑 1:只写一个方法:比如只写__str__,那repr(apple)会用默认的内存地址输出;只写__repr__print(apple)会用__repr__的结果,不够友好。建议两个都写。
  • 坑 2:repr不够详细:比如__repr__只写f"Fruit({self.name})",开发者无法通过这个输出重建对象,失去了__repr__的核心意义。

5. __getitem__:让对象支持 “下标访问”

列表能用list[0],字典能用dict["key"],那咱的 Fruit 对象能做到吗?默认不行,但加了__getitem__方法就可以!咱让 Fruit 支持两种下标访问:

  • 数字下标:fruit[0]→name,fruit[1]→color,fruit[2]→quantity;
  • 字符串下标:fruit["name"]→name,fruit["color"]→color,fruit["quantity"]→quantity。

5.1 实现__getitem__方法

修改后的Fruit类(新增__getitem__):

代码语言:python
代码运行次数:0
运行
复制
class Fruit:

   def __init__(self, name, color, quantity):

       self.name = name

       self.color = color

       self.quantity = quantity

   def __eq__(self, other):

       if not isinstance(other, Fruit):

           return False

       return (self.name == other.name

               and self.color == other.color

               and self.quantity == other.quantity)

   def __format__(self, format_spec):

       if format_spec == "s":

           return f"{self.name}({self.color},{self.quantity}个)"

       elif format_spec == "c":

           return f"{self.color}{self.name}"

       elif format_spec == "q":

           return f"{self.name}:{self.quantity}个"

       else:

           return f"Fruit(name={self.name}, color={self.color}, quantity={self.quantity})"

   def __or__(self, other):

       if not isinstance(other, Fruit):

           raise TypeError("只能合并Fruit类型的对象")

       if self.name != other.name:

           raise ValueError(f"不能合并不同种类的水果:{self.name} 和 {other.name}")

       merged_color = "".join(set([self.color, other.color]))

       merged_quantity = self.quantity + other.quantity

       return Fruit(self.name, merged_color, merged_quantity)

   def __str__(self):

       return f"{self.color}{self.name}({self.quantity}个)"

   def __repr__(self):

       return f'Fruit(name="{self.name}", color="{self.color}", quantity={self.quantity})'

   # 新增__getitem__:支持下标访问

   def __getitem__(self, key):

       # 1. 处理数字下标

       if key == 0:

           return self.name

       elif key == 1:

           return self.color

       elif key == 2:

           return self.quantity

       # 2. 处理字符串下标

       elif key == "name":

           return self.name

       elif key == "color":

           return self.color

       elif key == "quantity":

           return self.quantity

       # 3. 处理无效下标

       else:

           raise KeyError(f"无效的下标:{key}(支持0/1/2或'name'/'color'/'quantity')")

5.2 测试效果

现在 Fruit 对象能像列表 / 字典一样用下标访问了:

代码语言:python
代码运行次数:0
运行
复制
apple = Fruit("苹果", "红色", 5)

# 1. 数字下标访问

print(apple[0])  # 输出:苹果(对应name)

print(apple[1])  # 输出:红色(对应color)

print(apple[2])  # 输出:5(对应quantity)

# 2. 字符串下标访问

print(apple["name"])     # 输出:苹果

print(apple["color"])    # 输出:红色

print(apple["quantity"]) # 输出:5

# 3. 无效下标(测试错误处理)

try:

   apple[3]

except KeyError as e:

   print(e)  # 输出:无效的下标:3(支持0/1/2或'name'/'color'/'quantity')

try:

   apple["price"]

except KeyError as e:

   print(e)  # 输出:无效的下标:price(支持0/1/2或'name'/'color'/'quantity')

5.3 常见问题 & 注意事项

  • 坑 1:没处理无效下标:如果不写else分支,传无效下标会返回None,而不是报错,开发者很难排查问题。一定要主动抛KeyErrorIndexError
  • 坑 2:下标逻辑混乱:比如apple[0]返回 quantity,apple["name"]返回 color,会让使用者 confusion。下标对应关系要清晰,最好在文档或错误信息里说明。

汇总:常见问题 & 避坑指南

方法

常见坑

避坑方案

__eq__

没判断 other 类型,导致 AttributeError

先加isinstance(other, 类名)判断

__eq__

只比较部分属性,导致逻辑错误

根据业务场景,比较全量或核心属性

__format__

没处理默认格式化符,返回 None

加 else 分支,返回默认字符串

__format__

返回非字符串类型,导致 TypeError

确保所有分支都返回字符串

__or__

修改原对象,而不是返回新对象

新建实例返回,不修改 self/other

__or__

没处理不同类型的合并(比如 Fruit 和 int)

isinstance(other, Fruit)判断

__str__/repr__

只写一个方法,输出不友好 / 不详细

两个方法都写,遵循 “用户友好 / 开发者详细” 原则

__repr__

输出不完整,无法重建对象

包含所有初始化参数,格式对应__init__

__getitem__

没处理无效下标,返回 None

抛 KeyError/IndexError,说明支持的下标

__getitem__

下标对应关系混乱

清晰定义下标逻辑,在错误信息里说明

面试高频题 & 参考答案(大白话版)

1. 问:__eq__方法和==运算符是什么关系?默认的__eq__做什么?

答:==运算符其实就是调用对象的__eq__方法。比如a == b,本质是执行a.__eq__(b)。默认的__eq__比较的是 “对象的内存地址”,相当于is运算符 —— 哪怕两个对象属性一样,只要是不同实例,就返回 False。所以咱需要重写__eq__,按属性比较。

2. 问:__str____repr__有啥区别?分别在什么时候调用?

答:核心区别是 “给谁看”:

  • __str__是给用户看的,要友好易懂,比如print(对象)str(对象)、f-string 都会调用它;
  • __repr__是给开发者看的,要详细准确(最好能重建对象),比如repr(对象)、交互式环境直接输对象会调用它。 默认情况下,两者都打印内存地址,但重写后就按咱定义的来。

3. 问:__getitem__方法有啥用?怎么让对象支持像列表一样的切片(比如obj[1:3])?

答:__getitem__能让对象支持下标访问(比如obj[key]),key 可以是数字、字符串甚至切片对象。要支持切片的话,在__getitem__里判断 key 是不是slice类型就行 —— 比如if isinstance(key, slice),然后用key.startkey.stopkey.step处理切片逻辑,最后返回切片后的结果(比如新列表)。

4. 问:__or__方法对应哪个运算符?实现时要注意什么?

答:__or__对应|运算符(按位或),但咱可以自定义它的逻辑(比如合并对象)。实现时要注意两点:

  1. 不要修改原对象(selfother),要返回新对象 —— 不然原对象会被污染;
  2. 要处理类型错误,比如 other 不是目标类型时,主动抛 TypeError,别让它报 AttributeError。

5. 问:重写__eq__后,要不要重写__hash__?为什么?

答:要!因为 Python 有个规则:“如果两个对象的__eq__返回 True,那它们的__hash__必须返回相同的值”。默认的__hash__是根据内存地址计算的,重写__eq__后,哪怕两个对象__eq__返回 True,默认__hash__还是不同,这会导致对象在字典、集合里出问题(比如集合里存两个__eq__为 True 的对象,会存成两个,而不是一个)。所以重写__eq__时,一定要同步重写__hash__—— 比如def __hash__(self): return hash((self.name, self.color, self.quantity)),用__eq__比较的属性来计算 hash 值。

最后:实践建议

这 5 个 Dunder 方法是 Python 类的 “核心工具”,光看不行,得自己动手试:

  1. 复制上面的Fruit类代码,跑一遍所有测试案例,感受每个方法的效果;
  2. 扩展功能:比如给Fruit__add__(对应+运算符,实现数量相加)、__len__(返回数量,支持len(apple));
  3. 结合实际场景:比如用__getitem__让自定义的Student类支持student["score"]访问成绩。

掌握这些方法,你写的 Python 类会更灵活、更符合 Python 风格,编程效率也会翻倍!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 5 个超实用 Python Dunder 方法!掌握类的核心特性
    • 1. __eq__:让对象能比较 “相等”
      • 1.1 默认情况的坑
      • 1.2 实现__eq__方法
      • 1.3 测试效果
      • 1.4 常见问题 & 注意事项
    • 2. __format__:自定义格式化输出
      • 2.1 实现__format__方法
      • 2.2 测试效果
      • 2.3 常见问题 & 注意事项
    • 3. __or__:实现对象的 “合并” 操作
      • 3.1 实现__or__方法
      • 3.2 测试效果
      • 3.3 常见问题 & 注意事项
    • 4. __str__ & __repr__:控制对象的 “字符串表示”
      • 4.1 两者的核心区别
      • 4.2 实现__str__和__repr__
      • 4.3 测试效果
      • 4.4 常见问题 & 注意事项
    • 5. __getitem__:让对象支持 “下标访问”
      • 5.1 实现__getitem__方法
      • 5.2 测试效果
      • 5.3 常见问题 & 注意事项
    • 汇总:常见问题 & 避坑指南
    • 面试高频题 & 参考答案(大白话版)
      • 1. 问:__eq__方法和==运算符是什么关系?默认的__eq__做什么?
      • 2. 问:__str__和__repr__有啥区别?分别在什么时候调用?
      • 3. 问:__getitem__方法有啥用?怎么让对象支持像列表一样的切片(比如obj[1:3])?
      • 4. 问:__or__方法对应哪个运算符?实现时要注意什么?
      • 5. 问:重写__eq__后,要不要重写__hash__?为什么?
    • 最后:实践建议
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档