python基础语法之并发基础练习

一、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则继续传播异常
相关推荐
长和信泰光伏储能1 小时前
探索绿色能源:光伏发电的渠道与应用
python·能源
#麻辣小龙虾#1 小时前
网页布局css中flex+margin的组合布局技巧
开发语言·前端·javascript
郝学胜-神的一滴1 小时前
AI 编程智能体 20:技术栈选型与架构落地全攻略
人工智能·python·架构·langchain·1024程序员节·智能体
云和数据.ChenGuang1 小时前
python和java的try
java·开发语言·python
我是唐青枫1 小时前
Kotlin Inheritance(继承)详解:语法、原理、实战 Demo
开发语言·kotlin
ly76891 小时前
从 OpenAPI 到多语言 SDK 生成:Java 服务端契约漂移与生成器版本治理
java·开发语言·openapi·java 21·版本兼容·契约漂移·sdk 生成
lqbzlll2 小时前
CVE-2026-91403 排查指南:Java JSSE 信任链漏洞的根因、影响范围和修复步骤
java·开发语言·信任链
CarIise2 小时前
AOP 概念的引入
java·开发语言
凉茶钱2 小时前
多态:从虚函数语法到虚表寻址,一次讲透动态绑定
开发语言·c++·多态