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

Python 개선 제안 한국어 번역

PEP 528 – Windows 콘솔 인코딩을 UTF-8로 변경

Author:
Steve Dower <steve.dower at python.org>
Status:
Final
Type:
Standards Track
Created:
27-Aug-2016
Python-Version:
3.6
Post-History:
01-Sep-2016, 04-Sep-2016
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

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

초록

역사적으로 Python은 Windows 운영 체제와 상호작용하기 위해 흔히 C 런타임 함수를 통해 ANSI API를 사용해 왔습니다. 하지만 이러한 방식은 UTF-16 API를 사용하는 편이 권장되면서 오랫동안 지양되어 왔습니다. 운영 체제 내부에서 모든 텍스트는 UTF-16으로 표현되며, ANSI API는 활성 코드 페이지를 사용해 인코딩과 디코딩을 수행합니다.

이 PEP는 Windows에서 기본 표준 스트림 구현을 Unicode API를 사용하도록 변경할 것을 제안합니다. 이를 통해 사용자는 기본 Windows 콘솔에서 전체 범위의 Unicode 문자를 출력하고 입력할 수 있게 됩니다. 이는 또한 토크나이저가 readline 훅에서 텍스트를 파싱하는 방식에 미묘한 변경을 요구합니다.

구체적인 변경 사항

_io.WindowsConsoleIO 추가

현재는 표준 입력, 출력, 오류를 나타내는 파일 디스크립터를 감싸기 위해 _io.FileIO의 인스턴스가 사용됩니다. 저희는 Windows 콘솔 함수, 구체적으로는 ReadConsoleWWriteConsoleW를 사용하여 원시 IO 객체 역할을 하는 (C로 구현된) 새 클래스 _io.WindowsConsoleIO를 추가합니다.

이 클래스는 레거시 모드 플래그가 적용되지 않은 상태에서, 파일 디스크립터로 표준 스트림을 열 때 해당 스트림이 리디렉션된 파일이 아니라 콘솔 버퍼인 경우에 사용됩니다. 그렇지 않으면 오늘날과 마찬가지로 _io.FileIO가 사용됩니다.

이는 utf-8로 인코딩되어 전달된 텍스트를 요구하는 원시(바이트) IO 클래스이며, 해당 텍스트는 utf-16-le로 디코딩되어 Windows API에 전달됩니다. 마찬가지로, 이 클래스로부터 읽어들인 바이트는 운영 체제에 의해 utf-16-le로 제공되며 Python에 반환될 때 utf-8로 변환됩니다.

TextIOWrapper를 우회하여 표준 스트림에 ASCII 바이트를 직접 쓰는 코드(예를 들어, Twisted’s process_stdinreader.py)와의 호환성을 유지하려면 ASCII 호환 인코딩을 사용해야 합니다. ASCII 외의 특정 인코딩을 표준 스트림에 대해 가정하는 코드는 깨질 가능성이 높습니다.

_PyOS_WindowsConsoleReadline 추가

대화형 프롬프트에서 유니코드 입력을 허용하려면 새로운 readline 훅이 필요합니다. 기존 PyOS_StdioReadline 함수는 콘솔 버퍼인 파일 디스크립터로부터 읽어들이고 레거시 모드 플래그가 활성화되어 있지 않을 때 새로운 _PyOS_WindowsConsoleReadline 함수에 위임합니다(로직은 위와 동일해야 합니다).

readline 인터페이스는 널 문자가 포함되지 않은 8비트 인코딩 문자열을 반환해야 하므로, _PyOS_WindowsConsoleReadline 함수는 운영체제에서 읽어들인 utf-16-le을 utf-8로 변환합니다.

현재 sys.stdin에서 인코딩을 얻는 PyRun_InteractiveOneObject 함수는 레거시 모드 플래그가 활성화되어 있지 않는 한 utf-8을 선택합니다. 이는 readline 훅이 인코딩을 utf-8로 변경하도록 요구하거나, 올바른 동작을 위해 레거시 모드를 요구할 수 있습니다.

레거시 모드 추가

환경 변수 PYTHONLEGACYWINDOWSSTDIO를 설정한 채로 Python을 실행하면 레거시 모드 플래그가 활성화되며, 이는 이전 동작을 완전히 복원합니다.

대안적 접근법

win_unicode_console package는 콘솔의 기본 동작을 변경하는 순수 Python 대안입니다. 이는 순수 Python 코드를 사용하여 본질적으로 여기서 설명한 것과 동일한 수정 사항을 구현합니다.

깨질 수 있는 코드

다음 코드 패턴들은 이 변경의 결과로 깨지거나 다른 동작을 보일 수 있습니다. 이 코드 예제들은 모두 눈에 띄는 변화를 방지해줄 더 편리한 래퍼 대신 raw 파일 객체를 사용하도록 명시적으로 선택해야 합니다.

표준 입출력 인코딩 가정

sys.stdin.buffersys.stdout.buffer에 필요한 인코딩이 'mbcs'이거나 더 구체적인 인코딩이라고 가정하는 코드는 현재 우연히 동작하고 있을 수 있지만, 이 변경으로 인해 문제가 발생할 수 있습니다. 예를 들어:

>>> sys.stdout.buffer.write(text.encode('mbcs'))
>>> r = sys.stdin.buffer.read(16).decode('cp437')

이 코드를 수정하려면 암시적으로든 명시적으로든 TextIOWrapper에 지정된 인코딩을 사용해야 합니다.:

>>> # Fix 1: Use wrapper correctly
>>> sys.stdout.write(text)
>>> r = sys.stdin.read(16)

>>> # Fix 2: Use encoding explicitly
>>> sys.stdout.buffer.write(text.encode(sys.stdout.encoding))
>>> r = sys.stdin.buffer.read(16).decode(sys.stdin.encoding)

raw 객체를 잘못 사용하기

raw IO 객체를 사용하면서 부분 읽기와 쓰기를 올바르게 처리하지 않는 코드는 영향을 받을 수 있습니다. 이는 읽기에서 특히 중요한데, 입력이 훨씬 더 긴 UTF-8 문자열로 인코딩되는 것을 막을 실질적인 방법이 없기 때문에 읽히는 문자 수는 허용된 바이트 수의 4분의 1을 결코 초과하지 않습니다.:

>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(15)
abcdefghijklm
b'abc'
# data contains at most 3 characters, and never more than 12 bytes
# error, as "defghijklm\r\n" is passed to the interactive prompt

이 코드를 수정하려면 버퍼링된 리더/라이터를 사용하거나, 호출자가 버퍼가 가득 찰 때까지 계속 읽어야 합니다.:

>>> # Fix 1: Use the buffered reader/writer
>>> stdin = sys.stdin.buffer
>>> data = stdin.read(15)
abcedfghijklm
b'abcdefghijklm\r\n'

>>> # Fix 2: Loop until enough bytes have been read
>>> raw_stdin = sys.stdin.buffer.raw
>>> b = b''
>>> while len(b) < 15:
...     b += raw_stdin.read(15)
abcedfghijklm
b'abcdefghijklm\r\n'

작은 버퍼로 raw 객체 사용하기

raw IO 객체를 사용하여 4개 미만의 문자를 읽으려는 코드는 이제 오류를 받게 됩니다. UTF-8로 표현될 때 단일 문자 하나가 최대 4바이트를 필요로 할 수 있기 때문에, 요청은 실패해야 합니다.:

>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: must read at least 4 bytes

유일한 해결 방법은 더 큰 버퍼를 전달하는 것입니다.:

>>> # Fix: Request at least four bytes
>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(4)
a
b'a'
>>> >>>

(추가된 >>>는 입력 버퍼에 남아 있는 줄바꿈 문자 때문이며, 이 상황에서는 예상된 동작입니다.)