Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 782 – 添加 PyBytesWriter C API

作者:
Victor Stinner <vstinner at python.org>
讨论至:
Discourse 帖子
状态:
最终版
类型:
标准跟踪
创建日期:
2025年3月27日
Python 版本:
3.15
发布历史:
2025年2月18日
决议:
2025年9月11日

目录

重要

本 PEP 是一份历史文档。最新的、权威的文档现在可以在 PyBytesWriter API 中找到。

×

有关如何提出更改,请参阅 PEP 1

摘要

添加新的 PyBytesWriter C API 以创建 bytes 对象。

软弃用 PyBytes_FromStringAndSize(NULL, size)_PyBytes_Resize() API。这些 API 将不可变的 bytes 对象视为可变对象。它们仍然可用并维护,不会发出弃用警告,但编写新代码时不再推荐使用。

基本原理

禁止创建不完整/不一致的对象

使用 PyBytes_FromStringAndSize(NULL, size)_PyBytes_Resize() 创建 Python bytes 对象会将不可变的 bytes 对象视为可变对象。这违反了 bytes 对象不可变的原则。它还会创建一个不完整或“无效”的对象,因为字节未初始化。在 Python 中,bytes 对象应该始终完全初始化其字节。

低效的分配策略

当创建字节字符串且输出大小未知时,一种策略是分配一个短缓冲区,并在每次需要更大写入时将其扩展(到精确大小)。

这种策略效率低下,因为它需要多次扩大缓冲区。在第一次需要更大写入时,过度分配缓冲区会更有效。它减少了昂贵的 realloc() 操作次数,这可能意味着内存复制。

规范

API

type PyBytesWriter
PyBytesWriter_Create() 创建的 Python bytes 写入器实例。

该实例必须通过 PyBytesWriter_Finish()PyBytesWriter_Discard() 销毁。

创建、完成、丢弃

PyBytesWriter *PyBytesWriter_Create(Py_ssize_t size)
创建 PyBytesWriter 以写入 size 字节。

如果 size 大于零,则分配 size 字节,并将写入器大小设置为 size。调用者负责使用 PyBytesWriter_GetData() 写入 size 字节。

出错时,设置异常并返回 NULL。

size 必须为正数或零。

PyObject *PyBytesWriter_Finish(PyBytesWriter *writer)
完成由 PyBytesWriter_Create() 创建的 PyBytesWriter

成功时,返回一个 Python bytes 对象。出错时,设置异常并返回 NULL

无论如何,调用后写入器实例都无效。

PyObject *PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
类似于 PyBytesWriter_Finish(),但在创建 bytes 对象之前将写入器调整为 size 字节。
PyObject *PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
类似于 PyBytesWriter_Finish(),但在创建 bytes 对象之前使用 buf 指针调整写入器大小。

如果 buf 指针超出内部缓冲区边界,则设置异常并返回 NULL

函数伪代码

Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer);
return PyBytesWriter_FinishWithSize(writer, size);
void PyBytesWriter_Discard(PyBytesWriter *writer)
丢弃由 PyBytesWriter_Create() 创建的 PyBytesWriter

如果 writerNULL,则不执行任何操作。

调用后写入器实例无效。

高级 API

int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
writer 内部缓冲区增长 size 字节,在 writer 末尾写入 size 字节的 bytes,并将 size 添加到 writer 大小。

如果 size 等于 -1,则调用 strlen(bytes) 获取字符串长度。

成功时,返回 0。出错时,设置异常并返回 -1

int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
类似于 PyBytes_FromFormat(),但直接在写入器末尾写入输出。按需增长写入器内部缓冲区。然后将写入大小添加到写入器大小。

成功时,返回 0。出错时,设置异常并返回 -1

获取器

Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
获取写入器大小。
void *PyBytesWriter_GetData(PyBytesWriter *writer)
获取写入器数据:内部缓冲区的起始位置。

该指针在对 writer 调用 PyBytesWriter_Finish()PyBytesWriter_Discard() 之前有效。

低级 API

int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
将写入器大小调整为 size 字节。它可用于扩大或缩小写入器。

新分配的字节未初始化。

成功时,返回 0。出错时,设置异常并返回 -1

size 必须为正数或零。

int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
通过在当前写入器大小上添加 grow 字节来调整写入器大小。

新分配的字节未初始化。

成功时,返回 0。出错时,设置异常并返回 -1

size 可以是负数来缩小写入器。

void *PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
类似于 PyBytesWriter_Grow(),但同时更新 buf 指针。

如果内部缓冲区在内存中移动,则 buf 指针也会移动。buf 在内部缓冲区内的相对位置保持不变。

出错时,设置异常并返回 NULL

buf 不能是 NULL

函数伪代码

Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer);
if (PyBytesWriter_Grow(writer, size) < 0) {
    return NULL;
}
return (char*)PyBytesWriter_GetData(writer) + pos;

过度分配

PyBytesWriter_Resize()PyBytesWriter_Grow() 会过度分配内部缓冲区,以减少 realloc() 调用的次数,从而减少内存复制。

PyBytesWriter_Finish() 会修剪过度分配:它在创建最终 bytes 对象时将内部缓冲区缩小到精确大小。

线程安全

该 API 不是线程安全的:一个写入器应仅由单个线程同时使用。

软弃用

软弃用 PyBytes_FromStringAndSize(NULL, size)_PyBytes_Resize() API。这些 API 将不可变的 bytes 对象视为可变对象。它们仍然可用并维护,不会发出弃用警告,但编写新代码时不再推荐使用。

PyBytes_FromStringAndSize(str, size) 未软弃用。只有带有 NULL str 的调用才被软弃用。

示例

高级 API

创建字节字符串 b"Hello World!"

PyObject* hello_world(void)
{
    PyBytesWriter *writer = PyBytesWriter_Create(0);
    if (writer == NULL) {
        goto error;
    }
    if (PyBytesWriter_WriteBytes(writer, "Hello", -1) < 0) {
        goto error;
    }
    if (PyBytesWriter_Format(writer, " %s!", "World") < 0) {
        goto error;
    }
    return PyBytesWriter_Finish(writer);

error:
    PyBytesWriter_Discard(writer);
    return NULL;
}

创建字节字符串“abc”

创建固定大小为 3 字节的字节字符串 b"abc" 的示例

PyObject* create_abc(void)
{
    PyBytesWriter *writer = PyBytesWriter_Create(3);
    if (writer == NULL) {
        return NULL;
    }

    char *str = PyBytesWriter_GetData(writer);
    memcpy(str, "abc", 3);
    return PyBytesWriter_Finish(writer);
}

GrowAndUpdatePointer() 示例

使用指针写入字节并跟踪写入大小的示例。

创建字节字符串 b"Hello World"

PyObject* grow_example(void)
{
    // Allocate 10 bytes
    PyBytesWriter *writer = PyBytesWriter_Create(10);
    if (writer == NULL) {
        return NULL;
    }

    // Write some bytes
    char *buf = PyBytesWriter_GetData(writer);
    memcpy(buf, "Hello ", strlen("Hello "));
    buf += strlen("Hello ");

    // Allocate 10 more bytes
    buf = PyBytesWriter_GrowAndUpdatePointer(writer, 10, buf);
    if (buf == NULL) {
        PyBytesWriter_Discard(writer);
        return NULL;
    }

    // Write more bytes
    memcpy(buf, "World", strlen("World"));
    buf += strlen("World");

    // Truncate the string at 'buf' position
    // and create a bytes object
    return PyBytesWriter_FinishWithPointer(writer, buf);
}

更新 PyBytes_FromStringAndSize() 代码

使用软弃用的 PyBytes_FromStringAndSize(NULL, size) API 的代码示例

PyObject *result = PyBytes_FromStringAndSize(NULL, num_bytes);
if (result == NULL) {
    return NULL;
}
if (copy_bytes(PyBytes_AS_STRING(result), start, num_bytes) < 0) {
    Py_CLEAR(result);
}
return result;

现在可以更新为

PyBytesWriter *writer = PyBytesWriter_Create(num_bytes);
if (writer == NULL) {
    return NULL;
}
if (copy_bytes(PyBytesWriter_GetData(writer), start, num_bytes) < 0) {
    PyBytesWriter_Discard(writer);
    return NULL;
}
return PyBytesWriter_Finish(writer);

更新 _PyBytes_Resize() 代码

使用软弃用的 _PyBytes_Resize() API 的代码示例

PyObject *v = PyBytes_FromStringAndSize(NULL, size);
if (v == NULL) {
    return NULL;
}
char *p = PyBytes_AS_STRING(v);

// ... fill bytes into 'p' ...

if (_PyBytes_Resize(&v, (p - PyBytes_AS_STRING(v)))) {
    return NULL;
}
return v;

现在可以更新为

PyBytesWriter *writer = PyBytesWriter_Create(size);
if (writer == NULL) {
    return NULL;
}
char *p = PyBytesWriter_GetData(writer);

// ... fill bytes into 'p' ...

return PyBytesWriter_FinishWithPointer(writer, p);

参考实现

拉取请求 gh-131681.

CPython 参考实现说明,不属于规范

  • 实现内部分配一个 bytes 对象,因此 PyBytesWriter_Finish() 只返回对象而无需复制内存。
  • 对于不超过 256 字节的字符串,使用小的内部原始字节缓冲区。这避免了调整 bytes 对象的大小,因为这效率低下。最后,PyBytesWriter_Finish() 从这个小缓冲区创建 bytes 对象。
  • 使用空闲列表来降低在堆内存上分配 PyBytesWriter 的成本。

向后兼容性

对向后兼容性没有影响,只添加了新的 API。

PyBytes_FromStringAndSize(NULL, size)_PyBytes_Resize() API 已被软弃用。这些函数在使用时不会发出新的警告,也不打算移除。

先前的讨论


来源:https://github.com/python/peps/blob/main/peps/pep-0782.rst

最后修改:2025-09-18 13:28:58 GMT