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은 파일 이름, 환경 변수, 표준 스트림 등에 로케일 인코딩을 사용합니다. 로케일 인코딩은 로케일에서 상속되며, 인코딩과 로케일은 긴밀하게 결합되어 있습니다.
많은 사용자가 “C” 로케일이라고도 하는 POSIX 로케일에서 ASCII 인코딩을 상속받지만, 여러 가지 이유로 로케일을 변경할 수 없습니다. 이 인코딩은 유니코드 지원 측면에서 매우 제한적이어서, ASCII가 아닌 문자는 어떤 것이든 문제를 일으킬 가능성이 높습니다.
정확한 로케일을 얻는 것이 항상 쉬운 일은 아닙니다. 로케일은 Linux 배포판, FreeBSD, macOS 등에 따라 정확히 같은 이름을 갖지 않습니다. 그리고 최근의 C.UTF-8 로케일과 같은 일부 로케일은 소수의 플랫폼에서만 지원됩니다. 현재 로케일은 같은 플랫폼에서도 상황에 따라 달라질 수 있습니다. 예를 들어 SSH 연결은 같은 기기의 파일 시스템 인코딩이나 로컬 터미널 인코딩과 다른 인코딩을 사용할 수 있습니다.
반면에 Python 3.6은 macOS, Android, Windows(PEP 529)에서 이미 대부분의 함수에 기본적으로 UTF-8을 사용하고 있습니다. 다만 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 명령줄 도구와 연관되기 때문입니다.
하지만 사용자는 파일에 대해서는 다른 기대를 합니다. 파일은 올바르게 인코딩되어 있을 것으로 기대되며, JPEG 그림을 텍스트 모드로 여는 것처럼 open()이 잘못된 옵션으로 호출되면 Python이 일찍 실패할 것으로 기대됩니다. 이러한 이유로 open()의 기본 오류 핸들러는 strict로 유지됩니다.
최상의 하위 호환성을 위해 기본적으로는 변경하지 않음
UTF-8은 대부분의 경우 완벽하지만, 때로는 로케일 인코딩이 실제로 가장 좋은 인코딩입니다.
이 PEP는 POSIX 로케일의 동작을 변경합니다. 이 로케일은 보통 ASCII 인코딩과 같지만, UTF-8이 훨씬 더 나은 선택이기 때문입니다. 위험이나 회귀를 막기 위해 다른 로케일의 동작은 변경하지 않습니다.
이러한 다른 로케일에서 새 UTF-8 모드를 명시적으로 활성화하는 것은 사용자의 책임이므로, UTF-8 모드로 인해 발생할 수 있는 모지바케(mojibake) 문제에 대한 책임도 사용자에게 있습니다.
제안
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로 설정된 경우에만 파이썬 자식 프로세스에 영향을 주는 반면, 로케일 강제 변환은 모든 자식 프로세스에 영향을 주는 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() | 로케일/strict | UTF-8/strict |
| os.fsdecode(), os.fsencode() | 로케일/surrogateescape | UTF-8/surrogateescape |
| sys.stdin, sys.stdout | 로케일/strict | UTF-8/surrogateescape |
| sys.stderr | 로케일/backslashreplace | UTF-8/backslashreplace |
비교하자면, Python 3.6은 다음을 사용합니다:
| 함수 | 기본값 | POSIX 로케일 |
|---|---|---|
| open() | 로케일/strict | 로케일/strict |
| os.fsdecode(), os.fsencode() | 로케일/surrogateescape | 로케일/surrogateescape |
| sys.stdin, sys.stdout | 로케일/strict | 로케일/surrogateescape |
| sys.stderr | 로케일/backslashreplace | 로케일/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: Add a new UTF-8 Mode
- 2017-04: [Python-Dev] Proposed BDFL Delegate update for PEPs 538 & 540 (assuming UTF-8 for *nix system boundaries)
- 2017-01: [Python-ideas] PEP 540: Add a new UTF-8 Mode
- 2017-01: bpo-28180: Implementation of the PEP 538: coerce C locale to C.utf-8 (msg284764)
- 2016-08-17: bpo-27781: Change sys.getfilesystemencoding() on Windows to 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.