[Final] PEP 383 - Non-decodable Bytes in System Character Interfaces
원문 링크: PEP 383 - Non-decodable Bytes in System Character Interfaces
상태: Final 유형: Standards Track 작성일: 22-Apr-2009
PEP 383 – 시스템 문자 인터페이스의 디코딩 불가능한 바이트 처리
개요 (Abstract)
POSIX 시스템에서 파일 이름, 환경 변수, 명령줄 인수는 문자 데이터로 정의되어 있습니다. 그러나 C API는 특정 인코딩을 따르든 따르지 않든 관계없이 임의의 바이트를 전달할 수 있습니다. 이 PEP는 원본 바이트 문자열을 재구성할 수 있도록 문자열 내에 바이트를 삽입하는 방식으로 이러한 불규칙성을 처리하는 방법을 제안합니다.
배경 (Rationale)
C 언어의 char
타입은 문자 데이터와 바이트를 모두 나타내는 데 일반적으로 사용됩니다. 특정 POSIX 인터페이스는 문자 데이터를 처리하도록 명시되어 있고 널리 이해되고 있지만, 시스템 호출 인터페이스는 이러한 데이터의 인코딩에 대한 가정을 하지 않으며 있는 그대로 전달합니다. Python 3에서는 문자열이 유니코드 기반의 내부 표현을 사용하므로, C 인터페이스처럼 바이트 문자열의 인코딩을 무시하기 어렵습니다.
반면, Microsoft Windows NT는 Unix의 원래 설계 한계를 수정하여, 유니코드 기반 API를 제공함으로써 파일 이름, 환경 변수, 명령줄 인수가 실제로 문자 데이터임을 시스템 인터페이스에 명시했습니다 (하위 호환성을 위해 C-char 기반 API도 유지).
Python 3에서 제안된 한 가지 해결책은 두 가지 API 세트를 제공하는 것이었습니다. 하나는 바이트 지향(byte-oriented) API이고, 다른 하나는 문자 지향(character-oriented) API인데, 문자 지향 API는 모든 데이터를 정확하게 나타낼 수 없는 한계가 있었습니다. 불행히도 Windows의 경우 상황은 정반대였습니다. 바이트 지향 인터페이스는 모든 데이터를 나타낼 수 없고, 오직 문자 지향 API만 가능했습니다. 결과적으로, 크로스 플랫폼 방식으로 모든 사용자 데이터를 지원하려는 라이브러리 및 애플리케이션은 Python 2.x에서 끝없는 문제를 야기했던 바이트와 문자의 혼합을 받아들여야 했습니다.
이 PEP를 통해 이러한 데이터를 문자로 균일하게 처리하는 것이 가능해집니다. 균일성은 특정 인코딩 알고리즘을 사용하여 달성되는데, 이는 동일한 인코딩을 사용하는 경우에만 POSIX 시스템에서 데이터를 다시 바이트로 변환할 수 있음을 의미합니다.
이러한 문자열을 균일하게 처리할 수 있게 되면 애플리케이션 개발자는 운영 체제에 특정한 세부 사항에서 추상화할 수 있고, 한 API가 실패할 때 다른 API는 작동했을 위험을 줄일 수 있습니다.
명세 (Specification)
Windows에서는 Python이 wide character
API를 사용하여 문자 지향 API에 접근하며, 환경 데이터를 Python str
객체로 직접 변환할 수 있습니다 (PEP 277).
POSIX 시스템에서 Python은 현재 로케일(locale)의 인코딩을 적용하여 바이트 데이터를 유니코드로 변환하며, 디코딩할 수 없는 문자에 대해서는 실패합니다. 이 PEP를 통해 디코딩 불가능한 바이트(>= 128)는 lone surrogate
코드 U+DC80..U+DCFF로 표현됩니다. 128 미만의 바이트는 예외를 발생시킵니다.
디코딩 불가능한 바이트를 변환하기 위해 새로운 에러 핸들러(PEP 293)인 “surrogateescape”가 도입되며, 이 핸들러는 이러한 surrogate
를 생성합니다. 인코딩 시, 이 에러 핸들러는 surrogate
를 해당 바이트로 다시 변환합니다. 이 에러 핸들러는 파일 이름, 명령줄 인수 또는 환경 변수를 받거나 생성하는 모든 API에서 사용됩니다.
에러 핸들러 인터페이스는 encode
에러 핸들러가 유니코드 문자열을 반환하여 다시 인코딩되는 것 외에도, 바이트 문자열을 즉시 반환할 수 있도록 확장됩니다.
Python 3.0에 이미 존재하는 바이트 지향 인터페이스는 이 명세의 영향을 받지 않습니다. 이러한 인터페이스는 강화되지도, 사용 중단되지도 않습니다.
파일 이름을 처리하는 외부 라이브러리(예: GUI 파일 선택기)도 이 PEP에 따라 파일 이름을 인코딩해야 합니다.
논의 (Discussion)
surrogateescape
인코딩은 Markus Kuhn이 “UTF-8b”라고 불렀던 아이디어에 기반합니다.
디코딩 불가능한 바이트에 대해 균일한 API를 제공하지만, 이 인터페이스는 데이터가 surrogateescape
에러 핸들러를 사용하여 다시 바이트로 변환되는 경우에만 선택된 표현이 “작동”한다는 한계가 있습니다. 로케일의 인코딩과 (기본) 엄격(strict) 에러 핸들러로 데이터를 인코딩하면 예외가 발생하며, UTF-8로 인코딩하면 의미 없는 데이터가 생성됩니다.
다른 소스에서 얻은 데이터는 이 PEP에서 생성된 데이터와 충돌할 수 있습니다. 이러한 충돌을 처리하는 것은 이 PEP의 범위를 벗어납니다.
이 PEP는 문자열 내에 바이트를 “밀반입(smuggling)”할 가능성을 허용합니다. 만약 바이트가 대상 시스템에서 문자로 해석될 때 경로명 구분자와 같이 보안에 중요한 경우, 이는 보안 위험이 될 수 있습니다. 이러한 이유로 PEP는 128 미만의 바이트 밀반입을 거부합니다. 대상 시스템이 EBCDIC를 사용하는 경우, 이러한 밀반입된 바이트는 여전히 보안 위험이 될 수 있으며, 예를 들어 대괄호나 백슬래시를 밀반입할 수 있습니다. Python은 현재 EBCDIC를 지원하지 않으므로 실제로는 문제가 되지 않습니다. Python을 EBCDIC 시스템으로 포팅하는 사람은 에러 핸들러를 조정하거나 보안 위험을 해결하기 위한 다른 접근 방식을 고려해야 할 수 있습니다.
ASCII와 호환되지 않는 인코딩은 이 명세에서 지원되지 않습니다. ASCII 범위에서 디코딩에 실패하는 바이트는 예외를 발생시킵니다. 이러한 인코딩은 로케일 문자셋으로 사용되어서는 안 된다는 것이 널리 합의되어 있습니다.
대부분의 애플리케이션의 경우, 시스템 인터페이스에서 받은 데이터를 결국 동일한 시스템 인터페이스로 다시 전달한다고 가정합니다. 예를 들어, os.listdir()
를 호출하는 애플리케이션은 결과 문자열을 os.stat()
또는 open()
과 같은 API로 다시 전달할 가능성이 높으며, 이 API는 문자열을 원래의 바이트 표현으로 다시 인코딩합니다. 원본 바이트 문자열을 처리해야 하는 애플리케이션은 파일 시스템 인코딩으로 문자열을 인코딩하고, 에러 핸들러 이름으로 “surrogateescape”를 전달하여 얻을 수 있습니다. 예를 들어, os.listdir
와 유사하게 작동하지만 바이트를 허용하고 반환하는 함수는 다음과 같이 작성할 수 있습니다.
def listdir_b(dirname):
fse = sys.getfilesystemencoding()
dirname = dirname.decode(fse, "surrogateescape")
for fn in os.listdir(dirname): # fn is now a str object
yield fn.encode(fse, "surrogateescape")
이 PEP에서 제안하는 encode
에러 핸들러 인터페이스 확장은 ‘surrogateescape’ 에러 핸들러를 구현하는 데 필요합니다. 왜냐하면 교체 유니코드에서 생성될 수 없는 필요한 바이트 시퀀스가 있기 때문입니다. 그러나 encode
에러 핸들러 인터페이스는 현재 소스 문자열에서 인코딩 불가능한 유니코드 대신 교체 유니코드를 제공하도록 요구합니다. 그런 다음 즉시 해당 교체 유니코드를 인코딩합니다. 여기서 제안하는 ‘surrogateescape’와 같은 일부 에러 핸들러에서는 인코더가 원하는 바이트를 생성할 유니코드를 계산하도록 강제하는 대신, 에러 핸들러가 미리 인코딩된 교체 바이트 문자열을 제공하는 것이 더 간단하고 효율적입니다.
몇 가지 다른 접근 방식이 제안되었습니다.
- 내장 바이트를 지원하는 새로운 문자열 서브클래스 생성
NUL
문자 또는 자주 사용되지 않는 문자에 매핑하는 것과 같은 다른 이스케이프 스키마 사용
이러한 제안 중, 각 바이트 XX
를 시퀀스 U+0000 U+00XX
로 이스케이프하는 접근 방식은 UTF-8로 인코딩할 때 UTF-8 시퀀스에 NUL
바이트를 도입한다는 단점이 있습니다. 결과적으로 C 라이브러리는 문자열이 계속됨에도 불구하고 이를 문자열 종료로 해석할 수 있습니다. 특히, GTK 라이브러리는 이 경우 텍스트를 자를 것이고, 다른 라이브러리도 유사한 문제를 보일 수 있습니다.
참고 자료 (References)
UTF-8b https://web.archive.org/web/20090830064219/http://mail.nl.linux.org/linux-utf8/2000-07/msg00040.html
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments