学而不能致用的人是一头背着书的牛马。蠢驴是否知道它背上背着的是一堆书而不是一捆柴?—— 本杰明·富兰克林
前言
2025年2月底,我回顾了前五年学习工作经历以及技术发展趋势后,在编程语言方面树立以Python与C++为主的编程技能,Java、Rust、Go等其他语言为辅,这样我需要推动自己将Pyhon和C++的技能尽可能达到精通,熟记这两门语言核心知识。正如我在最近阅读的《富兰克林自传》中所看到,富兰克林早期以印刷技能作为自己的谋生手段,而近三年我也以编程技能作为自己的谋生手段,未来路还很长,不能止步于此,需要不断学习其他领域与学科知识,学以致用,不能成为芒格口中的“铁锤人”。
Python语言大概是在我五六年前开始正式作为工作语言,当时以《Head First Python》书籍入门,在读研期间通过阅读与实践深度学习相关项目,如Numpy、Pytorch、mmdetection、mmocr等,对Python语言有了更深的应用理解。最近通过阅读《流畅的Python》第2版,对该语言进行系统性地回顾与熟记;此次阅读Requests源码,一是对最近学习的一个总结与应用,二是给自己未来通过Python实现更大更复杂项目打下坚实的基础。
谁适合阅读这篇文章?
如何更好地阅读本篇文章?
项目简介
这里直接引用官方对Requests的相关介绍,我们在后面源码解析中会逐步展开代码中是如何实现这些功能的。
A simple, yet elegant, HTTP library. Requests是一个简单而优雅的 HTTP 库。
Supported Features & Best–Practices 支持的功能和最佳实践
Requests is ready for the demands of building robust and reliable HTTP–speaking applications, for the needs of today.
Requests已准备好满足构建强大且可靠的 HTTP 应用程序的需求,以满足当今时代的需求。
- Keep-Alive & Connection Pooling 保持连接和连接池
- International Domains & URLs 国际域名和 URL
- Sessions with Cookie Persistence 具有Cookie 持久性的会话机制
- Browser-style SSL Verification 浏览器风格的 SSL 验证
- Basic/Digest Authentication 基本身份验证和摘要身份认证
- Familiar dict–like Cookies 熟悉的类似dict的 Cookies
- Automatic Content Decompression and Decoding 内容自动解压缩和解码
- Muti-part File Uploads 多部分文件上传
- SOCKS Proxy Support 支持 SOCKS 代理
- Connection Timeouts 连接超时
- Streaming Downloads 流式下载
- Automatic honoring of .netrc 自动遵循.netrc配置
- Chunked HTTP Requests 分块 HTTP 请求
项目结构
我们再来看一下Requests的项目的顶层结构,并简单介绍一下各个目录与文件的作用。
这里采用eza -a -T -L 1 --group-directories-first
命令查看项目目录结构。
|
|
上面项目组织结构中,我们可以学习到一个广泛遵循的约定,即将项目的源码、测试、文档等内容分别放置在不同的目录中,以便于管理和维护。通常将源代码放在 src/ 目录,测试代码放在 tests/ 目录,文档资料放在 docs/ 目录,并使用 LICENSE 文件声明开源许可。
其他文件是一些Python项目工具的配置文件,如 pyproject.toml、setup.py、requirements-dev.txt、tox.ini 等,关于这些项目构建和运行的配置文件,平时在使用到这些工具时多多留意就好啦~
源码解析
本想寻找两三个简单实用的示例,无意间查看源码目录的src/requests/__init__.py
文件,发现已经有两个示例了,我们在这里直接运行它们。
|
|
下面正式开始对Requests源码进行解析,从src/requests/__init__.py
文件开始。
__init__.py
文件
以下截取文件代码中的核心部分,省略部分使用三行# ...
表示。
|
|
我们知道在Python中,__init__.py
文件的作用包含以下三点:
- 标记包目录:文件存在即表示该目录是一个Python包,允许导入子模块(空文件也可)。
- 初始化包:包被导入时自动执行,用于(1)定义包级变量/函数(如版本号、工具函数);(2)批量导入子模块,简化外部调用,如
from . import submodule
。 - 控制导入行为:(1)通过
__all__
指定from package import *
时导出的模块列表;(2)隐藏内部实现,仅暴露特定接口。
具体到上面展示的Requests中__init__.py
文件核心代码,主要包含了:
- 导入子模块,如
packages
、utils
; - 导入版本信息,如
__version__
; - 导入API接口,如
delete
、get
、head
、options
、patch
、post
、put
、request
; - 导入异常类,如
ConnectionError
、ConnectTimeout
、HTTPError
、JSONDecodeError
等; - 导入请求和响应模型类,如
PreparedRequest
、Request
、Response
; - 导入会话管理类,如
Session
、session
; - 导入状态码类,如
codes
; - 进行日志模块的初始化。
在阅读和解析其他模块源码之前,我们先来看下Requests核心模块的架构设计。其源码目录结构如下:
|
|
Requests的核心模块主要包含api.py
、sessions.py
、adapters.py
、models.py
、utils.py
、status_codes.py
,下面通过PlantUML图展示Requests的核心模块架构设计。
api.py
模块
在查看api.py
模块源码之前,我们将通过运行测试用例并逐步跟踪代码执行流程,这样可以更好地理解代码的执行逻辑。
理解 tests/
目录结构
将该目录下文件按照功能分类,方便查找和阅读。
- 核心功能测试:
test_requests.py
- 适配器与底层传输:
test_adapters.py
,test_lowlevel.py
- 工具与辅助功能:
test_utils.py
,utils.py
- 数据结构与内部类:
test_structures.py
- 钩子与扩展功能:
test_hooks.py
- 测试基础设施:
testserver/
,test_testserver.py
,certs/
- 兼容性与环境验证:
compat.py
- 帮助与文档:
test_help.py
- 包与安装验证:
test_packages.py
- 测试框架配置:
conftest.py
,__init__.py
安装测试依赖
运行下面命令安装测试依赖,包含 pytest
测试框架和 pytest-httpbin
本地 HTTP 服务模拟。
|
|
运行测试用例并进入调试模式
这里我们选择运行test_HTTP_200_OK_GET_ALTERNATIVE
测试方法,调试程序会在进入方法后暂停,方便我们跟踪代码执行流程。
|
|
接下来使用 n(下一步)、s(进入函数)等命令跟踪代码。
api.py
模块源码解析
本章我们重点关注api.py
模块,尤其是其中比较常用的requests.get()
和requests.post()
方法,上面示例的测试用例函数test_HTTP_200_OK_GET_ALTERNATIVE
中使用了requests.Request()
和requests.Session()
方法,我们在这里先不做详细展开。
我在test_requests.py
文件中找到了test_HTTP_200_OK_GET_WITH_PARAMS
测试用例,作为项目源码入手的起点,我们将逐步调试并解析这个测试用例。
此外,通过命令行中调试代码虽然方便,但是面对现代大型Python项目,采用IDE提供的调试工具更加方便。因此,创建以下VS Code调试配置文件。
.vscode/launch.json
具体内容如下:
|
|
同时,配置.vscode/settings.json
文件,指定 Python 解释器路径。这样,按下 F5 键即可在 VS Code 中调试test_HTTP_200_OK_GET_WITH_PARAMS
测试用例。
注意:在这里我踩了一个坑,一开始配置的
.vscode/launch.json
文件中configurations的args
参数写成了"args": ["tests/test_requests.py::test_HTTP_200_OK_GET_WITH_PARAMS", "--trace"]
,导致调试时一直提示找不到测试用例,后来将测试类TestRequests::
加上即可。分析我犯的错误的原因,主要有两个:
(1)利用人工智能文件编辑提示功能,自动生成了tests/test_requests.py:test_HTTP_200_OK_GET_ALTERNATIVE
,我想当然得用test_HTTP_200_OK_GET_WITH_PARAMS
将其替换;
(2)没有熟悉pytest的用法,尤其是区分pytest test_sample.py::TestMath::test_add
和pytest test_sample.py -k test_add
的区别,前者需指定测试类和测试方法,后者可以模糊匹配测试方法。
配置完后,可以在测试代码旁打断点,再按 F5 键开始调试,这样就可以在 VS Code 中逐步调试测试用例代码了。如下图:
参考资料
知识点记忆
- 提示词:模块、包。在Python中,模块是一个单独的 .py 文件,包是一个包含多个模块的文件夹,且必须有
__init__.py
文件。 - 提示词:主流工具。构建、打包和分发工具setuptools、测试框架pytest、代码格式化工具black、自动排序导入工具isort、代码静态分析工具flake8、自动化测试环境管理工具tox、文档生成工具Sphinx。