PEP 782 – PyBytesWriter C API를 추가합니다.
- Author:
- Victor Stinner <vstinner at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Type:
- Standards Track
- Created:
- 27-Mar-2025
- Python-Version:
- 3.15
- Post-History:
- 18-Feb-2025
- Resolution:
- 11-Sep-2025
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
개요
bytes 객체를 생성하는 새로운 PyBytesWriter C API를 추가합니다.
PyBytes_FromStringAndSize(NULL, size) 및 _PyBytes_Resize() API를 소프트 폐기합니다. 이러한 API는 변경할 수 없는 bytes 객체를 변경 가능한 객체로 취급합니다. 이러한 API는 계속 사용할 수 있고 유지 관리되며, 폐기 경고를 내보내지 않지만, 새 코드를 작성할 때는 더 이상 권장되지 않습니다.
근거
불완전하거나 일관되지 않은 객체 생성을 허용하지 않습니다.
PyBytes_FromStringAndSize(NULL, size) 및 _PyBytes_Resize()를 사용하여 Python bytes 객체를 생성하면 변경할 수 없는 bytes 객체를 변경 가능한 객체로 취급합니다. 이는 bytes 객체가 변경 불가능하다는 원칙에 어긋납니다. 또한 바이트가 초기화되지 않았으므로 불완전하거나 “유효하지 않은” 객체를 생성합니다. Python에서 bytes 객체는 항상 바이트가 완전히 초기화된 상태여야 합니다.
비효율적인 할당 전략
바이트 문자열을 생성할 때 출력 크기를 알 수 없다면, 짧은 버퍼를 할당한 다음 더 큰 쓰기가 필요할 때마다 버퍼를 (정확한 크기로) 확장하는 전략을 사용할 수 있습니다.
이 전략은 버퍼를 여러 번 확장해야 하므로 비효율적입니다. 더 큰 쓰기가 처음 필요할 때 버퍼를 초과 할당하는 편이 더 효율적입니다. 이렇게 하면 메모리 복사를 수반할 수 있는 비용이 큰 realloc() 작업의 횟수가 줄어듭니다.
사양
API
-
type PyBytesWriter
- A Python
byteswriter instance created byPyBytesWriter_Create().The instance must be destroyed by
PyBytesWriter_Finish()orPyBytesWriter_Discard().
생성, 완료, 폐기
-
PyBytesWriter *PyBytesWriter_Create(Py_ssize_t size)
- Create a
PyBytesWriterto write size bytes.If size is greater than zero, allocate size bytes, and set the writer size to size. The caller is responsible to write size bytes using
PyBytesWriter_GetData().On error, set an exception and return NULL.
size must be positive or zero.
-
PyObject *PyBytesWriter_Finish(PyBytesWriter *writer)
- Finish a
PyBytesWritercreated byPyBytesWriter_Create().On success, return a Python
bytesobject. On error, set an exception and returnNULL.The writer instance is invalid after the call in any case.
-
PyObject *PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
- Similar to
PyBytesWriter_Finish(), but resize the writer to size bytes before creating thebytesobject.
-
PyObject *PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
- Similar to
PyBytesWriter_Finish(), but resize the writer using buf pointer before creating thebytesobject.Set an exception and return
NULLif buf pointer is outside the internal buffer bounds.Function pseudo-code:
Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer); return PyBytesWriter_FinishWithSize(writer, size);
-
void PyBytesWriter_Discard(PyBytesWriter *writer)
- Discard a
PyBytesWritercreated byPyBytesWriter_Create().Do nothing if writer is
NULL.The writer instance is invalid after the call.
고수준 API
-
int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
- Grow the writer internal buffer by size bytes,
write size bytes of bytes at the writer end,
and add size to the writer size.
If size is equal to
-1, callstrlen(bytes)to get the string length.On success, return
0. On error, set an exception and return-1.
-
int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
- Similar to
PyBytes_FromFormat(), but write the output directly at the writer end. Grow the writer internal buffer on demand. Then add the written size to the writer size.On success, return
0. On error, set an exception and return-1.
접근자
-
Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
- Get the writer size.
-
void *PyBytesWriter_GetData(PyBytesWriter *writer)
- Get the writer data: start of the internal buffer.
The pointer is valid until
PyBytesWriter_Finish()orPyBytesWriter_Discard()is called on writer.
저수준 API
-
int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
- Resize the writer to size bytes. It can be used to enlarge or to
shrink the writer.
Newly allocated bytes are left uninitialized.
On success, return
0. On error, set an exception and return-1.size must be positive or zero.
-
int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
- Resize the writer by adding grow bytes to the current writer size.
Newly allocated bytes are left uninitialized.
On success, return
0. On error, set an exception and return-1.size can be negative to shrink the writer.
-
void *PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
- Similar to
PyBytesWriter_Grow(), but update also the buf pointer.The buf pointer is moved if the internal buffer is moved in memory. The buf relative position within the internal buffer is left unchanged.
On error, set an exception and return
NULL.buf must not be
NULL.Function pseudo-code:
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 객체를 변경 가능한 객체로 취급합니다. 이러한 API는 계속 사용할 수 있고 유지 관리되며 사용 중단 경고를 내보내지 않지만, 새 코드를 작성할 때는 더 이상 권장되지 않습니다.
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);
참조 구현
사양에 포함되지 않는 CPython 참조 구현에 관한 참고 사항입니다.
- 구현은 내부적으로
bytes객체를 할당하므로,PyBytesWriter_Finish()는 메모리를 복사하지 않고 객체를 그대로 반환합니다. - 256바이트 이하의 문자열에는 작은 내부 원시 바이트 버퍼가 사용됩니다. 이 방식은 비효율적인
bytes객체의 크기 조정을 수행할 필요를 없애 줍니다. 마지막에PyBytesWriter_Finish()는 이 작은 버퍼에서bytes객체를 생성합니다. - 힙 메모리에서
PyBytesWriter를 할당하는 비용을 줄이기 위해 프리 리스트가 사용됩니다.
하위 호환성
하위 호환성에는 영향이 없으며 새로운 API만 추가됩니다.
PyBytes_FromStringAndSize(NULL, size) 및 _PyBytes_Resize() API는 소프트 사용 중단 대상입니다. 이러한 함수를 사용해도 새로운 경고는 발생하지 않으며, 제거할 계획도 없습니다.
이전 논의
- 2025년 3월: 포인터 대신 크기를 사용하는 세 번째 공개 API 시도:
- 2025년 2월: 두 번째 공개 API 시도:
- 2024년 7월: 첫 번째 공개 API 시도:
- C API 워킹 그룹 결정: PyBytes_Writer() API 추가 (2024년 8월)
- 풀 리퀘스트 gh-121726: 첫 번째 공개 API 시도(2024년 7월)
- 2016년 3월: CPython에서 문자열을 생성하는 빠른 _PyAccu, _PyUnicodeWriter 및 _PyBytesWriter API: 원래 비공개
_PyBytesWriterC API에 관한 글입니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.