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

Python 개선 제안 한국어 번역

PEP 624 – Py_UNICODE 인코더 API 제거

Author:
Inada Naoki <songofacandy at gmail.com>
Status:
Final
Type:
Standards Track
Created:
06-Jul-2020
Python-Version:
3.11
Post-History:
08-Jul-2020

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 폐지 예정인 Py_UNICODE 인코더 API를 Python 3.11에서 제거할 것을 제안합니다:

  • PyUnicode_Encode()
  • PyUnicode_EncodeASCII()
  • PyUnicode_EncodeLatin1()
  • PyUnicode_EncodeUTF7()
  • PyUnicode_EncodeUTF8()
  • PyUnicode_EncodeUTF16()
  • PyUnicode_EncodeUTF32()
  • PyUnicode_EncodeUnicodeEscape()
  • PyUnicode_EncodeRawUnicodeEscape()
  • PyUnicode_EncodeCharmap()
  • PyUnicode_TranslateCharmap()
  • PyUnicode_EncodeDecimal()
  • PyUnicode_TransformDecimalToASCII()

Note

PEP 623은 Py_UNICODE와 관련된 유니코드 객체 API를 제거할 것을 제안합니다. 반면에 이 PEP는 유니코드 객체와 관련이 없습니다. 두 PEP는 동기가 서로 다르고 별도의 논의가 필요하기 때문에 나뉘어 있습니다.

동기

일반적으로 오랫동안 폐지 예정 상태였고 사용자가 적은 API의 수를 줄이는 것은 좋은 생각입니다. CPython의 유지 보수성을 높일 뿐만 아니라 API 사용자와 다른 Python 구현에도 도움이 되기 때문입니다.

근거

Python 3.3부터 폐지 예정

Py_UNICODE와 이를 사용하는 API는 Python 3.3부터 폐지 예정 상태입니다.

비효율성

이 API들은 모두 PyUnicode_FromWideChar를 사용하여 구현되어 있습니다. 따라서 사용자가 유니코드 객체를 인코딩하려 할 때 이 API들은 비효율적입니다.

널리 사용되지 않음

상위 4000개 PyPI 패키지 [1]를 검색한 결과, 이 API들을 사용하는 것은 pyodbc뿐입니다.

  • PyUnicode_EncodeUTF8()
  • PyUnicode_EncodeUTF16()

pyodbc는 이러한 API를 사용하여 유니코드 객체를 바이트열 객체로 인코딩합니다. 따라서 쉽게 수정할 수 있습니다. [2]

대체 API

Py_UNICODE * 대신 PyObject *unicode를 받는 대체 API가 있습니다. 사용자는 이러한 API로 이전할 수 있습니다.

폐지 예정 API 대체 API
PyUnicode_Encode() PyUnicode_AsEncodedString()
PyUnicode_EncodeASCII() PyUnicode_AsASCIIString() (1)
PyUnicode_EncodeLatin1() PyUnicode_AsLatin1String() (1)
PyUnicode_EncodeUTF7() (2)
PyUnicode_EncodeUTF8() PyUnicode_AsUTF8String() (1)
PyUnicode_EncodeUTF16() PyUnicode_AsUTF16String() (3)
PyUnicode_EncodeUTF32() PyUnicode_AsUTF32String() (3)
PyUnicode_EncodeUnicodeEscape() PyUnicode_AsUnicodeEscapeString()
PyUnicode_EncodeRawUnicodeEscape() PyUnicode_AsRawUnicodeEscapeString()
PyUnicode_EncodeCharmap() PyUnicode_AsCharmapString() (1)
PyUnicode_TranslateCharmap() PyUnicode_Translate()
PyUnicode_EncodeDecimal() (4)
PyUnicode_TransformDecimalToASCII() (4)

참고 사항:

  1. const char *errors 매개변수가 없습니다.
  2. 공개된 대체 API가 없습니다. 하지만 사용자는 대신 범용 PyUnicode_AsEncodedString()을 사용할 수 있습니다.
  3. const char *errors, int byteorder 매개변수가 없습니다.
  4. 직접적인 대체 API가 없습니다. 하지만 대신 Py_UNICODE_TODECIMAL을 사용할 수 있습니다. CPython은 유니코드를 숫자로 변환할 때 대신 _PyUnicode_TransformDecimalAndSpaceToASCII를 사용합니다.

계획

Python 3.11에서 이 API들을 제거합니다. 이 API들은 이미 폐지 예정으로 지정되었습니다.

  • PyUnicode_Encode()
  • PyUnicode_EncodeASCII()
  • PyUnicode_EncodeLatin1()
  • PyUnicode_EncodeUTF7()
  • PyUnicode_EncodeUTF8()
  • PyUnicode_EncodeUTF16()
  • PyUnicode_EncodeUTF32()
  • PyUnicode_EncodeUnicodeEscape()
  • PyUnicode_EncodeRawUnicodeEscape()
  • PyUnicode_EncodeCharmap()
  • PyUnicode_TranslateCharmap()
  • PyUnicode_EncodeDecimal()
  • PyUnicode_TransformDecimalToASCII()

대안 아이디어

Py_UNICODE*를 PyObject*로 대체

“Alternative APIs” 절에서 설명한 것처럼, 일부 API에는 PyObject *unicode 입력을 받는 공개 대체 API가 없습니다. 또한 일부 공개 대체 API에는 errors와 byteorder 매개변수가 없는 등의 제약이 있습니다.

폐지 예정인 API를 제거하는 대신, 그 이름을 공개 대체 API에 재사용할 수 있습니다.

이미 비공개 대체 API가 있으므로, 비공개 이름을 폐지 예정인 공개 이름으로 바꾸기만 하면 됩니다.

바꿀 이름 기존 이름
PyUnicode_EncodeASCII() _PyUnicode_AsASCIIString()
PyUnicode_EncodeLatin1() _PyUnicode_AsLatin1String()
PyUnicode_EncodeUTF7() _PyUnicode_EncodeUTF7()
PyUnicode_EncodeUTF8() _PyUnicode_AsUTF8String()
PyUnicode_EncodeUTF16() _PyUnicode_EncodeUTF16()
PyUnicode_EncodeUTF32() _PyUnicode_EncodeUTF32()

장점:

  • 더 일관된 API 집합을 갖게 됩니다.

단점:

  • 하위 호환성이 깨집니다.
  • 드문 사용 사례를 위해 유지 보수해야 할 공개 API가 늘어납니다.
  • 대부분의 사용 사례에는 기존 공개 API로 충분하며, 그 밖의 경우에는 PyUnicode_AsEncodedString()을 사용할 수 있습니다.

Py_UNICODE*를 Py_UCS4*로 교체

Py_UNICODE를 Py_UCS4로 교체하고 이 API들의 폐지 예정을 철회할 수 있습니다.

UTF-8, UTF-16, UTF-32 인코더는 내부적으로 Py_UCS4를 지원합니다. 따라서 PyUnicode_EncodeUTF8(), PyUnicode_EncodeUTF16(), PyUnicode_EncodeUTF32()는 임시 유니코드 객체를 만들지 않아도 됩니다.

장점:

  • UTF-8, UTF-16, UTF-32 코덱으로 Py_UCS4*를 바이트열 객체로 인코딩할 때 임시 유니코드 객체를 만들지 않아도 됩니다.

단점:

  • 하위 호환성이 깨집니다.
  • 드문 사용 사례를 위해 유지 관리해야 할 공개 API가 늘어납니다.
  • Python/C API를 지원하려는 다른 Python 구현도 이 API들을 지원해야 합니다.
  • 향후 유니코드 내부 표현을 UTF-8로 변경하면, 이 API들만을 위해 UCS-4 지원을 유지해야 합니다.

Py_UNICODE*를 wchar_t*로 대체

Py_UNICODE를 wchar_t로 대체할 수 있습니다. Py_UNICODE는 이미 wchar_t의 typedef이므로, 이는 현상 유지입니다.

sizeof(wchar_t) == 4인 플랫폼에서는 “Py_UNICODE*를 Py_UCS4*로 대체” 아이디어와 마찬가지로, UTF-8, UTF-16, UTF-32 코덱을 사용하여 wchar_t*에서 bytes 객체로 인코딩할 때 임시 유니코드 객체를 생성하지 않을 수 있습니다.

장점:

  • 하위 호환성이 유지됩니다.
  • sizeof(wchar_t) == 4인 플랫폼에서 UTF-8, UTF-16, UTF-32 코덱으로 Py_UCS4*에서 bytes 객체로 인코딩할 때 임시 유니코드 객체를 생성하지 않을 수 있습니다.

단점:

  • Windows는 wchar_t를 많이 사용하는 가장 주요한 플랫폼이지만, Windows에서는 sizeof(wchar_t) == 2이므로 이 API들은 항상 임시 유니코드 객체를 생성해야 합니다.
  • 드문 사용 사례를 위해 유지 관리해야 할 공개 API가 늘어납니다.
  • Python/C API를 지원하려는 다른 Python 구현도 이 API들을 지원해야 합니다.
  • 향후 유니코드 내부 표현을 UTF-8로 변경하면, 이 API들만을 위해 UCS-4 지원을 유지해야 합니다.

기각된 아이디어

런타임 경고 발생

기존 컴파일러 경고에 더해, 런타임에 DeprecationWarning을 발생시키자는 제안이 있었습니다.

하지만 이 API들은 현재 GIL을 해제하지 않습니다. 이러한 API에서 경고를 발생시키는 것은 안전하지 않습니다. 다음 예제를 참조하십시오.

PyObject *u = PyList_GET_ITEM(list, i);  // u is borrowed reference.
PyObject *b = PyUnicode_EncodeUTF8(PyUnicode_AS_UNICODE(u),
        PyUnicode_GET_SIZE(u), NULL);
// Assumes u is still living reference.
PyObject *t = PyTuple_Pack(2, u, b);
Py_DECREF(b);
return t;

PyUnicode_EncodeUTF8()에서 Python 경고를 발생시키면, 경고 필터와 다른 스레드가 list를 변경할 수 있으며, PyUnicode_EncodeUTF8()이 반환된 후 u가 댕글링 참조가 될 수 있습니다.

논의

반대 의견

  • 이 API들을 제거하면 임시 유니코드 객체 없이 코덱을 사용할 수 있는 기능이 사라집니다.
    • Python 3.3부터 코덱은 임시 유니코드 객체 없이 유니코드 버퍼를 직접 인코딩할 수 없습니다. 현재 이 API들은 모두 임시 유니코드 객체를 생성합니다. 따라서 이 API들을 제거해도 줄어드는 기능은 없습니다.
  • 디코더 API도 함께 제거하지 않는 이유는 무엇입니까?
    • 이 함수들은 안정 ABI의 일부입니다.
    • PyUnicode_DecodeASCII()와 PyUnicode_DecodeUTF8()은 매우 널리 사용됩니다. 이 함수들을 폐지할 만큼의 가치는 없습니다.
    • 디코더 API는 임시 bytes 객체를 만들지 않고 바이트 버퍼에서 직접 디코딩할 수 있습니다. 반면에 인코더 API는 임시 유니코드 객체를 피할 수 없습니다.

참고 문헌