Cursor 教我学 Python
最近加了很多 Python Coding 的任务,虽然在 AI 加持下能够顺利完成,但是还是觉得心理不踏实,觉得很多代码 AI 写完自己不是很懂,不喜欢这种感觉。需求是需要在客户请求大模型前,提前发送一次请求大模型,确保在客户请求的时候,就可以节省掉 tls 握手和 tcp 建立连接的时间,简称之为预热。之前写的是 golang ,为了用类比法更好的理解 yield ,可以在 golang 中实现
文章目录
1. 写在最前面
最近加了很多 Python Coding 的任务,虽然在 AI 加持下能够顺利完成,但是还是觉得心理不踏实,觉得很多代码 AI 写完自己不是很懂,不喜欢这种感觉。
刚好周日上午没事,抽空记录在 Python 开发中遇到的神奇语法和库。
2. Python 语法
2.1 yield
修改的代码中,函数的返回很多处用了 Yield 而不是 return ,这引起了我的好奇。
2.1.1 yield 和 return 的区别
在 Python 中,yield
和 return
都用于从函数中返回值,但它们之间有一些重要的区别:
-
返回类型
-
return
:-
return
语句用于结束函数的执行,并将一个值返回给调用者。函数在执行到return
时会立即退出。 -
每次调用函数时,都是从头开始执行,直到遇到
return
。
-
-
yield
:-
yield
语句用于定义生成器函数。生成器函数在执行时不会立即返回值,而是返回一个生成器对象。 -
当生成器函数被调用时,它不会立即执行,而是返回一个生成器对象。可以通过迭代这个生成器对象来逐步执行函数的代码,每次遇到
yield
时返回一个值,并在下一次迭代时从上次停止的地方继续执行。
-
-
-
内存使用
-
return
:- 返回一个完整的结果(例如一个列表),可能会占用较多内存,特别是当结果很大时。
-
yield
:- 生成器按需生成值,通常会更节省内存,特别是在处理大型数据集时,因为它不会一次性生成所有结果。
-
-
yield 的使用场景:
-
内存效率:适合处理大数据集,避免一次性加载。
-
无限序列:可生成无限序列,按需计算。
-
状态保持:在函数中保持状态,适合需要维护状态的场景。
-
协程:在异步编程中用于协作式多任务。
-
管道处理:构建数据处理管道,方便数据流动。
-
-
示例
-
使用
return
的函数:def get_squares(n): return [x*x for x in range(n)] squares = get_squares(5) print(squares) # 输出: [0, 1, 4, 9, 16]
-
使用
yield
的生成器:def get_squares(n): for x in range(n): yield x*x squares_gen = get_squares(5) for square in squares_gen: print(square) # 输出: 0, 1, 4, 9, 16
-
2.1.2 golang 中实现 yield 语法
之前写的是 golang ,为了用类比法更好的理解 yield ,可以在 golang 中实现一个类似能力的示例。
package main
import (
"fmt"
)
// 生成器函数,返回一个通道
func getSquares(n int) <-chan int {
ch := make(chan int) // 创建一个通道
go func() {
for x := 0; x < n; x++ {
ch <- x * x // 将计算结果发送到通道
}
close(ch) // 关闭通道,表示没有更多值
}()
return ch // 返回通道
}
func main() {
squares := getSquares(5) // 获取生成器通道
for square := range squares { // 迭代通道中的值
fmt.Println(square) // 输出: 0, 1, 4, 9, 16
}
}
函数说明:
-
通道:在
getSquares
函数中,我们创建了一个通道ch
,用于发送计算结果。 -
goroutine:我们使用
go
关键字启动了一个 goroutine,在这个 goroutine 中计算平方并将结果发送到通道。 -
关闭通道:当所有值都发送完后,我们关闭通道,以便接收方知道没有更多值可供接收。
-
迭代通道:在
main
函数中,我们使用range
迭代通道,从中接收值。
3. aiohttp 库
需求是需要在客户请求大模型前,提前发送一次请求大模型,确保在客户请求的时候,就可以节省掉 tls 握手和 tcp 建立连接的时间,简称之为预热。
3.1 原始写法
async def analyze_backend_consistency(num_requests: int = 5, delay: float = 0.5, concurrent: bool = False):
# 顺序发送请求
results = []
for i in range(num_requests):
try:
connector = aiohttp.TCPConnector(ssl=True)
async with aiohttp.ClientSession(connector=connector) as session:
result = await make_request(session, i + 1)
results.append(result)
if i < num_requests - 1:
await asyncio.sleep(delay)
except Exception as e:
print(f"请求 {i + 1} 失败: {str(e)}")
# 打印每个请求的结果
for result in results:
print(f"\n请求 {result['request_id']} 结果:")
print(f"远程IP:端口 = {result['remote_ip']}:{result['remote_port']}")
print(f"请求ID = {result['x_request_id']}")
print(f"处理时间 = {result['upstream_time']}ms")
print(f"总响应时间 = {result['response_time']}ms")
print("-" * 50)
# 分析结果
print("\n分析结果:")
unique_ips = set(r['remote_ip'] for r in results)
print(f"使用的不同IP数量: {len(unique_ips)}")
print(f"IP列表: {', '.join(str(ip) for ip in unique_ips)}")
# 计算平均响应时间
avg_response_time = sum(float(r['response_time']) for r in results) / len(results)
print(f"平均响应时间: {avg_response_time:.2f}ms")
# 检查是否所有请求都使用了同一个连接
print(f"所有请求是否使用同一个连接: {len(unique_ips) == 1}")
# 分析处理时间
upstream_times = [float(r['upstream_time']) for r in results]
print(f"后端处理时间范围: {min(upstream_times):.2f}ms - {max(upstream_times):.2f}ms")
print(f"后端处理时间平均值: {sum(upstream_times)/len(upstream_times):.2f}ms")
测试结果:
3.2 修改写法
async def analyze_backend_consistency(num_requests: int = 5, delay: float = 0.5, concurrent: bool = False):
connector = aiohttp.TCPConnector(ssl=True)
async with aiohttp.ClientSession(connector=connector) as session:
results = []
if concurrent:
# 并发发送所有请求
tasks = [make_request(session, i + 1) for i in range(num_requests)]
results = await asyncio.gather(*tasks)
else:
# 顺序发送请求
for i in range(num_requests):
try:
result = await make_request(session, i + 1)
results.append(result)
if i < num_requests - 1:
await asyncio.sleep(delay)
except Exception as e:
print(f"请求 {i + 1} 失败: {str(e)}")
# 打印每个请求的结果
for result in results:
print(f"\n请求 {result['request_id']} 结果:")
print(f"远程IP:端口 = {result['remote_ip']}:{result['remote_port']}")
print(f"请求ID = {result['x_request_id']}")
print(f"处理时间 = {result['upstream_time']}ms")
print(f"总响应时间 = {result['response_time']}ms")
print("-" * 50)
# 分析结果
print("\n分析结果:")
unique_ips = set(r['remote_ip'] for r in results)
print(f"使用的不同IP数量: {len(unique_ips)}")
print(f"IP列表: {', '.join(str(ip) for ip in unique_ips)}")
# 计算平均响应时间
avg_response_time = sum(float(r['response_time']) for r in results) / len(results)
print(f"平均响应时间: {avg_response_time:.2f}ms")
# 检查是否所有请求都使用了同一个连接
print(f"所有请求是否使用同一个连接: {len(unique_ips) == 1}")
# 分析处理时间
upstream_times = [float(r['upstream_time']) for r in results]
print(f"后端处理时间范围: {min(upstream_times):.2f}ms - {max(upstream_times):.2f}ms")
print(f"后端处理时间平均值: {sum(upstream_times)/len(upstream_times):.2f}ms")
测试结果:
3.2 耗时对比分析
原始写法 | 修改写法 | |
---|---|---|
特点 | 每个请求都创建新的 TCPConnector 和 ClientSession 每次请求都要重新建立 TCP 连接和 TLS 握手 不复用 HTTP/2 连接 每个请求的耗时 = TCP建立(~40ms) + TLS握手(~200ms) + 请求处理时间 |
只创建一次 TCPConnector 和 ClientSession TCP 连接和 TLS 握手只进行一次 复用 HTTP/2 连接 第一个请求耗时 = TCP建立 + TLS握手 + 请求处理时间 后续请求耗时 = 请求处理时间 |
4. 碎碎念
周日的时候写了 80%,今晚刚好手里的活搞的差不多了,给总结收个尾。上海最近的暴雨和雷声有点子吓人:
-
世间最重要的事莫过于懂得让自己属于自己。
-
偶尔觉得妈妈很丢人,妈妈为什么连起码的脸面的自尊都没有呢?我都觉得上火。比起她自己,她有更想守护的,那就是我。人真正变强大,不是因为守护着自尊心,而是抛开自尊心的时候。所以妈妈很强大。
这周就要回去看妈妈啦,开心,就写到这里吧。
5. 参考资料
- 如何理解Python中的yield用法?
- https://docs.aiohttp.org/en/stable/tracing_reference.html#aiohttp-client-tracing-reference
更多推荐
所有评论(0)