PEP 540 – 새로운 UTF-8 모드 추가
- Author:
- Victor Stinner <vstinner at python.org>
- BDFL-Delegate:
- INADA Naoki
- Status:
- Final
- Type:
- Standards Track
- Created:
- 05-Jan-2016
- Python-Version:
- 3.7
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Python의 UTF-8 사용을 향상하기 위해 새로운 “UTF-8 모드”를 추가합니다. UTF-8 모드가 활성화되면 Python은 다음을 수행합니다:
- 현재 플랫폼이 설정한 로케일과 관계없이
utf-8인코딩을 사용하고, stdin과stdout의 오류 처리기를surrogateescape로 변경합니다.
이 모드는 기본적으로 비활성화되어 있지만, “POSIX” 로케일을 사용하면 자동으로 활성화됩니다.
UTF-8 모드를 제어할 수 있도록 -X utf8 명령줄 옵션과 PYTHONUTF8 환경 변수를 추가합니다.
근거
로케일 인코딩과 UTF-8
Python 3.6은 파일 이름, 환경 변수, 표준 스트림 등에서 로케일 인코딩을 사용합니다. 로케일 인코딩은 로케일에서 상속되며, 인코딩과 로케일은 밀접하게 결합되어 있습니다.
많은 사용자는 POSIX 로케일, 즉 “C” 로케일에서 ASCII 인코딩을 상속받지만, 여러 가지 이유로 로케일을 변경할 수 없습니다. 이 인코딩은 유니코드 지원 측면에서 매우 제한적이므로, ASCII가 아닌 문자는 문제를 일으킬 가능성이 높습니다.
정확한 로케일을 얻는 일이 항상 쉬운 것은 아닙니다. 로케일은 서로 다른 Linux 배포판, FreeBSD, macOS 등에서 정확히 동일한 이름을 사용하지 않습니다. 또한 최근의 C.UTF-8 로케일과 같은 일부 로케일은 소수의 플랫폼에서만 지원됩니다. 현재 로캘은 컨텍스트에 따라 동일한 플랫폼에서도 달라질 수 있습니다. 예를 들어, SSH 연결은 동일한 시스템에서 파일 시스템 인코딩이나 로컬 터미널 인코딩과 다른 인코딩을 사용할 수 있습니다.
반면 Python 3.6은 대부분의 함수에서 이미 macOS, Android 및 Windows에서 기본적으로 UTF-8을 사용하고 있습니다 (PEP 529). 다만 여기서 open()은 주목할 만한 예외입니다. UTF-8은 Python 스크립트, XML 및 JSON 파일 형식의 기본 인코딩이기도 합니다. Go 프로그래밍 언어는 모든 문자열에 UTF-8을 사용합니다.
UTF-8은 최신 플랫폼에서 읽고 쓰는 데이터에 거의 보편적으로 지원됩니다. 또한 Python에서도 지원이 매우 우수합니다. 문제는 단순히 로케일이 자주 잘못 구성된다는 것입니다. 명백한 해결책은 로케일 인코딩을 무시하고 UTF-8을 사용하는 것입니다.
디코딩할 수 없는 바이트의 그대로 전달: surrogateescape
기본 strict 오류 처리기를 사용하여 UTF-8에서 바이트를 디코딩할 때, Python 3은 디코딩할 수 없는 첫 번째 바이트에서 UnicodeDecodeError를 발생시킵니다.
cat이나 grep과 같은 Unix 명령줄 도구와 대부분의 Python 2 애플리케이션에는 이러한 종류의 버그가 단순히 발생하지 않습니다. 데이터를 디코딩하지 않고 원시 바이트 시퀀스로 처리하기 때문입니다.
Python 3에는 이미 Unix 도구 및 Python 2처럼 동작할 수 있는 해결책인 surrogateescape 오류 처리기가 있습니다 (PEP 383). 이를 사용하면 실제로는 유니코드를 사용하면서도 데이터를 바이트인 것처럼 처리할 수 있으며, 디코딩할 수 없는 바이트는 서로게이트 문자로 저장됩니다.
UTF-8 모드는 stdin과 stdout에 surrogateescape 오류 처리기를 설정합니다. 이러한 스트림은 일반적으로 Unix 명령줄 도구와 관련되어 있기 때문입니다.
그러나 파일에 대해서는 사용자들이 다른 기대를 합니다. 파일은 올바르게 인코딩되어 있을 것으로 예상되며, 잘못된 옵션으로 open()을 호출하면 Python이 조기에 실패할 것으로 예상됩니다. 예를 들어 JPEG 이미지를 텍스트 모드로 여는 경우가 이에 해당합니다. 이러한 이유로 open()의 기본 오류 처리기는 strict로 유지됩니다.
최상의 하위 호환성을 위해 기본적으로 변경하지 않습니다.
UTF-8은 대부분의 경우 완벽하지만, 때로는 로캘 인코딩이 실제로 가장 적합한 인코딩입니다.
이 PEP는 POSIX 로캘에 대한 동작을 변경합니다. 이 로캘은 일반적으로 ASCII 인코딩과 동일한 반면 UTF-8이 훨씬 더 나은 선택이기 때문입니다. 위험이나 회귀를 방지하기 위해 다른 로캘에 대한 동작은 변경하지 않습니다.
사용자는 이러한 다른 로캘에서 새로운 UTF-8 모드를 명시적으로 활성화할 책임이 있으므로, UTF-8 모드로 인해 발생할 수 있는 모든 모지바케 문제에 대해서도 책임을 집니다.
제안
UTF-8 인코딩을 사용하고 로캘 인코딩을 무시하며 stdin 및 stdout 오류 처리기를 surrogateescape로 변경하는 새로운 UTF-8 모드를 추가합니다.
새로운 -X utf8 명령줄 옵션과 PYTHONUTF8 환경 변수를 추가합니다. 사용자는 명령줄 옵션 -X utf8을 사용하거나 환경 변수 PYTHONUTF8=1을 설정하여 UTF-8 모드를 명시적으로 활성화할 수 있습니다.
이 모드는 기본적으로 비활성화되며 POSIX 로캘에서는 활성화됩니다. 사용자는 명령줄 옵션 -X utf8=0을 사용하거나 환경 변수 PYTHONUTF8=0을 설정하여 UTF-8 모드를 명시적으로 비활성화할 수 있습니다.
표준 스트림의 경우 PYTHONIOENCODING 환경 변수가 UTF-8 모드보다 우선합니다.
Windows에서는 PYTHONLEGACYWINDOWSFSENCODING 환경 변수(PEP 529)가 UTF-8 모드보다 우선합니다.
UTF-8 모드의 효과:
sys.getfilesystemencoding()은'UTF-8'을 반환합니다.locale.getpreferredencoding()은UTF-8을 반환합니다. 해당 do_setlocale 인자와 로캘 인코딩은 무시됩니다.sys.stdin및sys.stdout의 오류 처리기는surrogateescape로 설정됩니다.
부작용:
open()은 기본적으로 UTF-8 인코딩을 사용합니다. 그러나 기본적으로strict오류 처리기를 계속 사용합니다.os.fsdecode()및os.fsencode()는 UTF-8 인코딩을 사용합니다.- 명령줄 인자, 환경 변수 및 파일 이름은 UTF-8 인코딩을 사용합니다.
로캘 강제 변환(PEP 538)과의 관계
POSIX 로캘은 로캘 강제 변환(PEP 538)과 UTF-8 모드(PEP 540)를 활성화합니다. 로캘 강제 변환이 활성화되면 UTF-8 모드를 활성화해도 추가 효과가 없습니다.
UTF-8 모드는 로캘 강제 변환과 동일한 효과를 냅니다.
sys.getfilesystemencoding()은'UTF-8'을 반환합니다.locale.getpreferredencoding()은UTF-8을 반환하며,sys.stdin및sys.stdout의 오류 처리기는surrogateescape로 설정됩니다.
이러한 변경 사항은 Python 코드에만 영향을 줍니다. 그러나 로캘 강제에는 추가적인 효과가 있습니다. LC_CTYPE 환경 변수와 LC_CTYPE 로캘이 C.UTF-8과 같은 UTF-8 로캘로 설정됩니다. 한 가지 부작용은 Python이 아닌 코드도 로캘 강제의 영향을 받는다는 것입니다. 두 PEP는 상호 보완적입니다.
Centos 7과 같이 로캘 강제가 지원되지 않는 플랫폼에서는 POSIX 로캘이 UTF-8 모드만 활성화합니다. 이 경우 Python 코드는 UTF-8 인코딩을 사용하고 로캘 인코딩을 무시하는 반면, Python이 아닌 코드는 로캘 인코딩을 사용하며, POSIX 로캘에서는 일반적으로 ASCII입니다.
UTF-8 모드는 모든 플랫폼에서 지원되고 어떤 로캘로도 활성화할 수 있지만, 로캘 강제는 모든 플랫폼에서 지원되는 것이 아니며 POSIX 로캘로 제한됩니다.
UTF-8 모드는 PYTHONUTF8 환경 변수가 1로 설정된 경우에만 Python 자식 프로세스에 영향을 주는 반면, 로캘 강제는 모든 자식 프로세스에 영향을 주는 LC_CTYPE 환경 변수를 설정합니다.
로캘 강제 방식의 이점은 바이너리 확장 모듈과 자식 프로세스의 인코딩 처리가 Python의 인코딩 처리와 일관되도록 하는 데 도움이 된다는 것입니다. UTF-8 모드 방식의 장점은 임베딩 애플리케이션이 프로세스 전역 로캘 설정을 변경하지 않고도 인터프리터의 동작을 변경할 수 있다는 것입니다.
하위 호환성
유일하게 하위 호환성이 없는 변경 사항은 POSIX 로캘이 이제 기본적으로 UTF-8 모드를 활성화한다는 것입니다. 이제 UTF-8 인코딩을 사용하고 로캘 인코딩을 무시하며, stdin 및 stdout의 오류 처리기를 surrogateescape로 변경합니다.
부록: 인코딩 및 오류 처리기
UTF-8 모드는 open(), os.fsdecode(), os.fsencode(), sys.stdin, sys.stdout 및 sys.stderr에서 사용하는 기본 인코딩과 오류 처리기를 변경합니다.
인코딩 및 오류 처리기
| 함수 | 기본값 | UTF-8 모드 또는 POSIX 로캘 |
|---|---|---|
| open() | locale/strict | UTF-8/strict |
| os.fsdecode(), os.fsencode() | locale/surrogateescape | UTF-8/surrogateescape |
| sys.stdin, sys.stdout | locale/strict | UTF-8/surrogateescape |
| sys.stderr | locale/backslashreplace | UTF-8/backslashreplace |
비교해 보면 Python 3.6은 다음을 사용합니다:
| 함수 | 기본값 | POSIX 로캘 |
|---|---|---|
| open() | locale/strict | locale/strict |
| os.fsdecode(), os.fsencode() | locale/surrogateescape | locale/surrogateescape |
| sys.stdin, sys.stdout | locale/strict | locale/surrogateescape |
| sys.stderr | locale/backslashreplace | locale/backslashreplace |
Windows의 인코딩 및 오류 처리기
Windows에서는 인코딩과 오류 처리기가 다릅니다:
| 함수 | 기본값 | 레거시 Windows FS 인코딩 | UTF-8 모드 |
|---|---|---|---|
| open() | mbcs/strict | mbcs/strict | UTF-8/strict |
| os.fsdecode(), os.fsencode() | UTF-8/surrogatepass | mbcs/replace | UTF-8/surrogatepass |
| sys.stdin, sys.stdout | UTF-8/surrogateescape | UTF-8/surrogateescape | UTF-8/surrogateescape |
| sys.stderr | UTF-8/backslashreplace | UTF-8/backslashreplace | UTF-8/backslashreplace |
이에 비해 Python 3.6에서는 다음을 사용합니다:
| 함수 | 기본값 | 레거시 Windows FS 인코딩 |
|---|---|---|
| open() | mbcs/strict | mbcs/strict |
| os.fsdecode(), os.fsencode() | UTF-8/surrogatepass | mbcs/replace |
| sys.stdin, sys.stdout | UTF-8/surrogateescape | UTF-8/surrogateescape |
| sys.stderr | UTF-8/backslashreplace | UTF-8/backslashreplace |
“레거시 Windows FS 인코딩”은 PYTHONLEGACYWINDOWSFSENCODING 환경 변수로 활성화됩니다.
stdin 및/또는 stdout이 파이프로 리디렉션되면, sys.stdin 및/또는 sys.stdout은 UTF-8 대신 기본적으로 mbcs 인코딩을 사용합니다. 그러나 UTF-8 모드에서는 sys.stdin 및 sys.stdout이 항상 UTF-8 인코딩을 사용합니다.
Note
Windows에는 POSIX 로캘이 없습니다. ANSI 코드 페이지가 로캘 인코딩으로 사용되며, 이 코드 페이지는 ASCII 인코딩을 사용하지 않습니다.
링크
- bpo-29240: PEP 540 구현: 새로운 UTF-8 모드 추가
- PEP 538: “레거시 C 로캘을 C.UTF-8로 강제 변환”
- PEP 529: “Windows 파일 시스템 인코딩을 UTF-8로 변경”
- PEP 528: “Windows 콘솔 인코딩을 UTF-8로 변경”
- PEP 383: “시스템 문자 인터페이스에서 디코딩할 수 없는 바이트”
게시물 기록
- 2017-12: [Python-Dev] PEP 540: 새 UTF-8 모드 추가
- 2017-04: [Python-Dev] PEP 538 및 540에 대한 BDFL 대리인 갱신 제안(*nix 시스템 경계에 UTF-8을 가정)
- 2017-01: [Python-ideas] PEP 540: 새 UTF-8 모드 추가
- 2017-01: bpo-28180: PEP 538의 구현: C 로케일을 C.utf-8로 강제 변환(msg284764)
- 2016-08-17: bpo-27781: Windows에서 sys.getfilesystemencoding()을 UTF-8로 변경(msg272916) – Victor가 PEP 529(Windows 파일 시스템 인코딩을 UTF-8로 변경)를 위해
-X utf8을 제안했습니다.
버전 기록
- 버전 4: UTF-8 모드에서
locale.getpreferredencoding()은 이제'UTF-8'을 반환합니다. - 버전 3: UTF-8 모드는 더 이상
open()의 기본 오류 처리기(strict)를 변경하지 않으며, Strict UTF-8 모드는 제거되었습니다. - 버전 2: PEP를 처음부터 다시 작성하여 훨씬 짧고 이해하기 쉽게 만들었습니다.
- 버전 1: python-dev에 게시된 첫 번째 버전입니다.
Copyright
This document has been placed in the public domain.