Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at the PyBytesWriter API.

×

See PEP 1 for how to propose changes.

초록

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
PyBytesWriter_Create()로 생성한 Python bytes 작성기 인스턴스입니다.

인스턴스는 PyBytesWriter_Finish() 또는 PyBytesWriter_Discard()로 파괴해야 합니다.

생성, 완료, 폐기

PyBytesWriter *PyBytesWriter_Create(Py_ssize_t size)
size 바이트를 쓰기 위한 PyBytesWriter를 생성합니다.

size가 0보다 크면 size 바이트를 할당하고 작성기 크기를 size로 설정합니다. 호출자는 PyBytesWriter_GetData()를 사용하여 size 바이트를 쓸 책임이 있습니다.

에러 시 예외를 설정하고 NULL을 반환합니다.

size는 양수이거나 0이어야 합니다.

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를 폐기합니다.

writer가 NULL이면 아무것도 하지 않습니다.

호출 후에는 작성기 인스턴스가 유효하지 않습니다.

고수준 API

int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
writer 내부 버퍼를 size 바이트만큼 늘리고, bytes의 size 바이트를 writer 끝에 쓴 다음, writer 크기에 size를 더합니다.

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는 양수이거나 0이어야 합니다.

int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
현재 writer 크기에 grow 바이트를 더하여 writer의 크기를 조정합니다.

새로 할당된 바이트는 초기화되지 않은 상태로 남습니다.

성공하면 0을 반환합니다. 오류가 발생하면 예외를 설정하고 -1을 반환합니다.

writer를 축소하기 위해 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 객체를 가변 객체처럼 취급합니다. 이 API들은 계속 사용할 수 있고 유지 관리되며 폐지 경고도 발생시키지 않지만, 새 코드를 작성할 때는 더 이상 권장되지 않습니다.

PyBytes_FromStringAndSize(str, size)는 소프트 폐지되지 않습니다. str이 NULL인 호출만 소프트 폐지됩니다.

예시

고수준 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는 소프트 폐지됩니다. 이 함수들을 사용해도 새로운 경고가 발생하지 않으며, 제거할 계획도 없습니다.

이전 논의