一、dataclass
1.1 题目
使用 @dataclass(eq=False) 定义一个 Req 类,将其实例放入 set 中,验证两个内容相同但对象不同的 Req 实例不会互相覆盖。
1.2 参考答案
python
from dataclasses import dataclass
@dataclass(eq=False)
class Req:
req_id: int
value: str
s1 = Req(1, "test")
s2 = Req(1, "test")
print(s1 == s2) # 表示两个对象是否相等
print(s1 is s2) # 表示两个对象是否是同一个对象
s = set([s1, s2])
print(s1 in s) # 表示对象s1是否在集合s中
print(s2 in s) # 表示对象s2是否在集合s中
执行结果:

1.3 常见概念
- dataclass: 自动生成类的构造方法等常用方法,python3.7开始提供了dataclasses模块
- eq=Flase:控制对象的相等性比较方式
- ==和is:值相等与对象身份的区别
- set:集合如何判断元素是否重复
- eq__和__hash:Python对象比较和哈希机制
1.4 详细说明
python
from dataclasses import dataclass
@dataclass
class Req:
req_id: int
payload: str
这段代码相当于自动生成了一些常用方法:
| 方法 | 作用 |
|---|---|
| init | 自动生成构造方法 |
| repr | 自动生成对象字符串表示 |
| eq | 自动生成相等性比较 |
| hash | 根据配置决定是否生成哈希方法 |
1.4.1 @dataclass介绍
python
@dataclass
class Req:
req_id: int
payload: str
@dataclass是一个装饰器
可以理解为:
python
class Req:
req_id: int
payload: str
Req = dataclass(Req)
即:dataclass接收一个类,对这个类进行增强,然后返回处理后的类。这是一种装饰器机制
1.4.2 eq=False作用
表示:不要自动为Req类生成基于字段内容的__eq__方法
默认eq=True

为什么输出结果变为True?
因为默认的dataclass会根据字段内容进行比较
相当于自动生成了类似这样的代码:
python
def __eq__(self, other):
if other.__class__ is self.__class__:
return (
self.req_id == other.req_id
and self.value == other.value
)
return NotImplemented
因此:r1.req_id == r2.req_id, r1.value == r2.value。所以结果为True,这叫做基于值的比较
使用eq==False,意味着dataclass不生成自己的__eq__。
如果类没有自己定义__req__,那么它会继承object.eq
而默认的对象比较方式是身份比较
因此:
python
r1 = Req(1, "test")
r2 = Req(1, "test")
print(r1 == r2)
输出:False
虽然两个对象的字段内容完全相同,但是它们是两个独立创建的对象
1.4.3 为什么可以放进set
涉及Python的哈希机制
1.4.3.1 set介绍
set是Python的集合类型
两个主要特征:
- 元素不重复
- 元素必须是可哈希的(hashable)
为什么集合可以去重:
因为它主要依赖:
- hash(): 计算哈希值
- eq(): 发生哈希冲突时判断元素是否相等
1.4.3.2 哈希机制工作原理
假设:s = {r1, r2}
Python大致的执行过程为:
python
插入 r1
|
v
计算 hash(r1)
|
v
放入集合
|
|
插入 r2
|
v
计算 hash(r2)
|
v
定位哈希槽位
|
v
是否发生哈希冲突?
|
+---+---+
| |
否 是
| |
v v
直接插入 调用 __eq__
|
是否相等?
/ \
是 否
| |
不插入 插入
为什么r1和r2都能放入集合?
关键在于:
python
@dataclass(eq=False)
当dataclass不生成__eq__时:
- 类继承object.eq
- 类也继承object.hash
- 对象默认可以哈希
因此:
python
print(hash(r1))
print(hash(r2))
通常会得到两个整数
1.4.3.3 eq和hash关系
| 配置 | 自动生成 __eq__ |
默认是否可哈希 | 原因 |
|---|---|---|---|
@dataclass |
是 | 否 | 默认eq=True,但frozen=False Python通常会将__hash__设置为None,因此不能放进set |
@dataclass(eq=False) |
否 | 是(继承 object) | 不生成__eq__,继承父类的身份比较和哈希机制 |
@dataclass(frozen=True) |
是 | 是(字段可哈希时) | 字段不可修改,并且默认生成基于字段的哈希方法 |
@dataclass(unsafe_hash=True) |
是 | 是(字段可哈希时 |
1.5 总结
Python 中 dataclass 的 eq=False 有什么作用?为什么它可以放入 set?
你可以这样回答:
dataclass 默认会根据字段生成 __eq__ 方法,比较两个对象的字段值。设置 eq=False 后,dataclass 不再生成这个方法,如果类本身也没有定义 __eq__,就会继承 object 的身份比较语义,同时继承 object 的 __hash__,因此对象默认可哈希,可以放入 set。两个字段相同但身份不同的对象不会被集合当成重复元素。
再记住一个核心规则:
Python 的 set 和 dict 依赖哈希机制,判断元素是否重复时,不能只看 hash,还要结合 == 判断。相等的对象必须具有相同的哈希值,但哈希值相同的对象不一定相等。
1.6 思考
可以自己先看下以下代码会输出什么结果:
python
from dataclasses import dataclass
@dataclass(eq=False)
class Req:
req_id: int
payload: str
def __eq__(self, other):
if not isinstance(other, Req):
return NotImplemented
return self.req_id == other.req_id
def __hash__(self):
return hash(self.req_id)
r1 = Req(1, 'hello')
r2 = Req(1, 'world')
print(r1 == r2)
print(r1 is r2)
print(Req.__hash__)
r1 = Req(1, 'hello')
r2 = Req(1, 'world')
d = {
r1: 'A',
r2: 'B'
}
print(len(d))
print(d)
二、
2.1 题目
写一个@contextmanager的"借出-归还"资源池,在with块里抛异常,验证资源仍被归还
2.2 参考答案
python
from contextlib import contextmanager
class my_context_manager:
def __init__(self):
self.resources = ["conn-1", "conn-2", "conn-3"]
@contextmanager # 可以让一个包含yield的生成器函数变成上下问管理器
def borrow(self):
resource = self.resources.pop() # 从资源池取出一个链接
print(f"Borrowing resource: {resource}")
try:
yield resource # 将资源交给调用者,暂停生成器,执行with代码块
finally:
self.resources.append(resource) # 无论是否发生异常,都会归还链接
print(f"Returning resource: {resource}")
pool = my_context_manager()
with pool.borrow() as resource: # 获取资源
try:
print(f"Using resource: {resource}")
raise ValueError("An error occurred while using the resource") # 模拟异常
except Exception as e:
print(f"Exception occurred: {e}")
finally:
pass
print(f"Available resources after context: {pool.resources}")
运行结果:

2.3 执行步骤
上述代码完整的实现了一个简单的资源池上下文管理器
- 获取资源
- 使用资源
- 归还资源
细节:当with内部发生异常,Python实际上会把异常重新抛回生成器yield的位置
因此,执行过程相当于
python
try:
yield resource
except Exception:
# 异常从 yield 位置抛入
raise
finally:
self.resources.append(resource)
所以finally仍然可以执行,资源可以归还
注意:如果pop()本身发生异常,或者append()本身失败,这段代码并不能保证资源一定能够成功归还。生产环境还需要考虑资源池并发安全、资源有效性和容量限制。
2.4 Python上下文管理器核心机制
2.4.1 __enter__的返回值为什么会赋值给conn
首先先看代码:
python
from contextlib import contextmanager
class ResourceManager:
def __enter__(self):
print("Acquiring resource")
return "conn-1"
def __exit__(self, exc_type, exc_val, exc_tb):
print("release resource")
return False
with ResourceManager() as conn:
print(f"Using resource: {conn}")
运行结果:

with ... as conn会将__enter__()的返回值赋给conn
2.4.2 __exit__为什么有三个异常参数
python
def __exit__(self, exc_type, exc_val, exc_tb):
这三个参数用于接收with代码块中发生的异常信息
| 参数 | 含义 | 示例 |
|---|---|---|
exc_type |
异常类型 | ValueError |
exc_val |
异常对象 / 异常信息 | invalid value |
exc_tb |
异常的 Traceback | 异常堆栈信息 |
例如:
with ResourceManager() as conn: raise ValueError("数据库连接异常")
此时 __exit__ 接收到的参数大致是:
exc_type = ValueErrorexc_val = ValueError("数据库连接异常")exc_tb = traceback对象
如果没有发生异常:
exc_type = Noneexc_val = Noneexc_tb = None
这样就可以在资源释放时,根据异常类型执行不同的清理逻辑。
2.4.3 __exit__返回True和False有什么区别
这里有一个重要的知识点:是否吞掉异常
python
def __exit__(self, exc_type, exc_val, exc_tb):
print("release resource")
return False
- 返回False或者None:即使执行了__exit__,异常依然会往外抛出
python
from contextlib import contextmanager
class ResourceManager:
def __enter__(self):
print("Acquiring resource")
return "conn-1"
def __exit__(self, exc_type, exc_val, exc_tb):
print("Releasing resource")
return False
with ResourceManager() as conn:
print(f"Using resource: {conn}")
raise ValueError("An error occurred while using the resource")
print("Continuing execution after context manager")

- 返回True:异常不会向外传播,程序可以继续执行
python
from contextlib import contextmanager
class ResourceManager:
def __enter__(self):
print("Acquiring resource")
return "conn-1"
def __exit__(self, exc_type, exc_val, exc_tb):
print("Releasing resource")
return True
with ResourceManager() as conn:
print(f"Using resource: {conn}")
raise ValueError("An error occurred while using the resource")
print("Continuing execution after context manager")

4.5 with的底层执行逻辑
可以将下面代码:
python
with ResourceManager() as conn:
print(conn)
理解成如下伪代码:
python
manager = ResourceManager
conn = manager.__enter__()
try:
print(conn)
except BaseException:
if not manager.__exit__(*sys.exc_info()):
raise
else:
manager.__exit__(None, None, None)
try负责尝试,except负责异常,else负责正常后续逻辑,finally负责收尾。因此当else中发生ValueError,前面的except Exception不能捕获到。
4.6 总结
- enter():获取资源,返回值赋给as后面的变量
- exit():退出上下文时执行,负责清理资源
- exit():返回True抑制异常,返回False或者None则继续传播异常