Python 扩展知识

作者:

在

Python 解释器有多种实现,它们各自有不同的设计目标和适用场景。其中最主流的是 CPython 和 PyPy,此外还有几个重要的其他实现。

下面我将为你介绍它们的特点,并重点对比 CPython 与 PyPy。

核心对比:CPython vs. PyPy

特性 CPython PyPy
实现语言 C 语言 Python 的一个子集(称为 RPython)
运行机制 解释执行字节码 即时编译,将频繁执行的代码编译为机器码
速度 解释执行,速度较慢 通常快 2-5 倍,特别适合长时间运行的程序
内存占用 相对较高 内存占用通常更小(得益于分代回收和压缩算法)
兼容性 官方标准,兼容性最好(几乎所有第三方库都支持) 高度兼容,但部分 C 扩展库(如 numpy, pandas)支持不佳
启动时间 快 较慢(因为 JIT 预热需要时间)
适用场景 通用开发、脚本、短生命周期的任务 长时间运行的应用(如 Web 后端)、计算密集型任务

补充说明:

PyPy 为什么不能完全兼容 C 扩展?

PyPy 和 CPython 的对象模型在内存结构上存在根本差异。为了让 PyPy 也能运行 C 扩展,需要一个叫做 cpyext 的模拟层,但这个模拟层的效率很低,通常会抵消掉JIT 带来的性能优势,让程序变得比 CPython 还要慢。因此,对于重度依赖 C 扩展的项目,PyPy 目前还不是一个合适的选择。

一、CPython 详解

1. 实现语言与架构

CPython 完全用 C 语言 编写。它的核心是一个字节码解释器,也叫做虚拟机。

工作流程:

  1. 源代码解析:将 .py 文件解析成抽象语法树。
  2. 编译为字节码:将 AST 编译成平台无关的字节码(例如 LOAD_FAST、BINARY_ADD、RETURN_VALUE)。
  3. 解释执行:主循环是一个巨大的 switch 语句,根据当前字节码指令跳转到对应的 C 代码段执行。

例如,a + b 会被编译成:

text

LOAD_FAST   a
LOAD_FAST   b
BINARY_ADD
RETURN_VALUE

解释器逐条执行这些指令。

2. 为什么 CPython 相对较慢?

  • 动态类型:每次 BINARY_ADD 都必须检查两个对象的类型(是整数?浮点数?列表?自定义对象?),然后分派到不同的加法逻辑。C 语言 int a+b 只是一条 CPU 指令。
  • 解释开销:每个字节码指令都要经历取指、解码、分派、执行、跳转,比直接执行机器码多几个数量级的开销。
  • 全局解释器锁:限制了多线程真正并行执行 Python 字节码(虽然 I/O 操作会释放锁)。

3. 优点

  • 稳定性与兼容性:官方实现,所有 PEP 必须先在此实现。几乎 100% 的第三方库(包括 C 扩展)都支持 CPython。
  • 调试与嵌入:C API 稳定,可以轻松用 C/C++ 写扩展模块,或者把 Python 嵌入到大型 C 项目中。
  • 启动速度快:没有 JIT 预热过程,适合一次性脚本。

二、PyPy 详解

1. 实现语言:RPython

PyPy 最独特的一点:它本身是用 Python 的一个子集——RPython 编写的。RPython 是一个受限制的 Python(没有动态类型、反射等功能,但足够表达解释器的逻辑)。

然后,PyPy 项目使用一个元跟踪 JIT 编译器生成工具链,将 RPython 代码编译成 C 语言,再编译成可执行文件。所以最终的 PyPy 解释器是一个经过 JIT 支持的二进制程序。

2. 核心:即时编译

PyPy 内部仍然先把 Python 源代码编译成字节码,但不会直接解释。它会:

  • 开始用解释器运行代码(慢)。
  • 同时跟踪哪些代码路径被频繁执行(比如循环、热点函数)。
  • 当某个循环执行次数超过阈值(如 1000 次),PyPy 的 JIT 会将那段字节码编译成机器码(x86/ARM 等)。
  • 下次执行直接运行机器码,并且会做优化(比如类型特化、内联、消除冗余操作)。

举例: for i in range(1000000): total += i 第一次迭代时,PyPy 会记录变量 i 和 total 都是整数类型。JIT 会生成专门处理整数的加法循环机器码,没有动态类型检查,速度接近 C。

3. 内存占用为什么更小?

CPython 采用引用计数作为主要垃圾回收机制,每个对象都有额外字段维护计数,并且不会自动移动对象,导致内存碎片。 PyPy 使用分代式垃圾回收(标记-清除 + 复制收集),会不定期压缩内存,将活对象移动到一起,释放碎片空间。因此长期运行的 PyPy 进程内存占用往往比 CPython 低 20%~50%。

4. 兼容性问题的根源

PyPy 的目标是替代 CPython,但最大障碍是 C 扩展模块(如 numpy、pandas 内部的 C 代码)。 原因:

  • CPython 暴露的 C API 假设对象的内存布局是固定的(比如 PyObject 结构体)。
  • PyPy 的对象布局完全不同(为了实现更快的 GC 和 JIT),所以需要 模拟层 cpyext。
  • cpyext 会在每次调用 C 扩展函数时,把 PyPy 对象“转换”成假的 CPython 对象,开销极大,甚至使性能比 CPython 还差。
  • 很多 C 扩展直接操作 CPython 的内部字段,这在 PyPy 上会崩溃。

所以结论:如果你的项目依赖 numpy/scipy/pandas/tensorflow 等,就不要用 PyPy。

5. 启动慢与预热

PyPy 启动时会加载 JIT 编译器和自己的运行时,比 CPython 启动慢 1-2 秒。而且 JIT 需要预热——最初几百次迭代仍然是解释执行,之后才会编译优化。因此非常短的程序(执行时间 < 0.1 秒)用 PyPy 反而更慢。


HTTP协议基础(请求/响应模型、状态码、请求方法)

HTTP(HyperText Transfer Protocol)是 Web 的基石,一个无状态、基于请求-响应模型的应用层协议。下面我会详细拆解它的核心概念。


一、请求-响应模型

HTTP 通信由客户端(如浏览器、App)发起请求,服务器处理并返回响应。每次交互都是独立的,服务器默认不保留上一次请求的状态(无状态)。

1. 请求结构

一个 HTTP 请求由四部分组成:

text

<方法> <路径> <版本>
<头部1>: <值1>
<头部2>: <值2>
...
<空行>
[请求体]

示例:

http

POST /api/user HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 27

{"name": "Alice", "age": 25}
  • 请求行:方法 + 请求目标 + 协议版本。
  • 头部:键值对,描述元数据(如 Host、User-Agent、Cookie)。
  • 空行:分隔头部与体。
  • 请求体:可选,常用于 POST/PUT 等方法携带数据。

2. 响应结构

text

<版本> <状态码> <原因短语>
<头部1>: <值1>
...
<空行>
[响应体]

示例:

http

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 52

{"id": 100, "name": "Alice", "age": 25}
  • 状态行:协议版本 + 状态码 + 原因短语(如 200 OK)。
  • 头部:服务器信息、内容类型、缓存策略等。
  • 空行。
  • 响应体:服务器返回的资源(HTML、JSON、图片等)。

3. HTTP 的特点

  • 无状态:每个请求独立,可通过 Cookie、Session 等机制模拟状态。
  • 文本协议(HTTP/1.x):可读性好,但效率较低;HTTP/2 和 HTTP/3 已改为二进制帧。
  • 默认端口:HTTP 为 80,HTTPS 为 443。

二、HTTP 状态码(Status Code)

状态码表示服务器对请求的处理结果,共五类,首位数字区分含义。

1xx – 信息性响应

  • 100 Continue:客户端应继续发送请求。
  • 101 Switching Protocols:服务器同意切换协议(如升级到 WebSocket)。

2xx – 成功

  • 200 OK:请求成功,响应体包含资源(GET)或操作结果(POST)。
  • 201 Created:资源创建成功(常用于 POST 响应),Location 头指示新资源 URL。
  • 204 No Content:请求成功,但无响应体(常用于 DELETE 或 PUT 更新)。
  • 206 Partial Content:范围请求成功,用于断点续传、分片下载。

3xx – 重定向

  • 301 Moved Permanently:永久重定向,客户端应更新书签。
  • 302 Found(临时重定向):请求的资源临时移至新 URL,客户端应继续使用原 URL。
  • 304 Not Modified:资源未修改,客户端可使用本地缓存(协商缓存)。
  • 307 Temporary Redirect:类似 302,但禁止改变请求方法(如 POST 重定向后仍是 POST)。
  • 308 Permanent Redirect:类似 301,且禁止改变请求方法。

4xx – 客户端错误

  • 400 Bad Request:语法错误或请求参数无效。
  • 401 Unauthorized:缺少认证信息或认证失败(通常需要登录)。
  • 403 Forbidden:服务器理解请求但拒绝执行(权限不足)。
  • 404 Not Found:资源不存在。
  • 405 Method Not Allowed:请求方法不被允许(如对静态文件 PUT)。
  • 408 Request Timeout:客户端未在服务器预备等待时间内发送完整请求。
  • 413 Payload Too Large:请求体过大。
  • 429 Too Many Requests:客户端发送请求过快(限流)。

5xx – 服务器错误

  • 500 Internal Server Error:服务器内部错误(代码异常、配置错误等)。
  • 502 Bad Gateway:网关或代理收到上游无效响应。
  • 503 Service Unavailable:服务器过载或维护中。
  • 504 Gateway Timeout:网关等待上游响应超时。

三、HTTP 请求方法(HTTP Verbs)

方法表明对资源期望执行的操作。RESTful API 中方法配合资源 URL 表达语义。

方法 作用 幂等性 安全性 是否有请求体 典型场景
GET 获取资源 是 是 否 浏览网页、查询数据
POST 创建资源或提交数据 否 否 是 表单提交、上传文件、新建订单
PUT 完整替换资源(全量更新) 是 否 是 更新用户信息(全量)
PATCH 部分更新资源 否 否 是 修改用户的部分字段
DELETE 删除资源 是 否 通常无 删除文章、用户
HEAD 仅获取响应头(不返回体) 是 是 否 检查资源是否存在、获取元信息
OPTIONS 查询服务器支持的请求方法 是 是 否 CORS 预检请求
TRACE 回显服务器收到的请求(调试) 是 是 否 诊断(通常禁用)
CONNECT 建立隧道(用于代理) 否 否 是 HTTPS 代理、WebSocket 隧道

重要概念解释

  • 幂等性:多次执行相同请求,对服务器资源的最终效果与一次执行相同。 例:GET /user/1 无论请求几次,都返回相同资源(若资源未变)。PUT /user/1 多次发送相同数据,结果仍是那份数据。POST /orders 多次提交可能创建多个订单(不幂等)。
  • 安全性:方法不会改变服务器资源(只读)。 安全方法:GET, HEAD, OPTIONS, TRACE。

RESTful 实践建议

  • 使用名词表示资源:/users 而不是 /getUsers。
  • 利用方法表达动作:GET /users(列表),POST /users(创建),PUT /users/1(替换),DELETE /users/1(删除)。
  • 对复杂查询使用查询参数:GET /users?role=admin。

总结表

组成部分 关键点
请求-响应模型 客户端发起请求,服务器响应;HTTP 无状态;请求含方法+路径+头+体;响应含状态码+头+体。
状态码 1xx 信息,2xx 成功,3xx 重定向,4xx 客户端错误,5xx 服务器错误。常见如 200, 404, 500。
请求方法 GET(获取)、POST(创建)、PUT(全量更新)、PATCH(部分更新)、DELETE(删除)、HEAD(仅头)等。