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

Python 개선 제안 한국어 번역

PEP 3138 – Python 3000의 문자열 표현

Author:
Atsuo Ishimoto <ishimoto at gembook.org>
Status:
Final
Type:
Standards Track
Created:
05-May-2008
Python-Version:
3.0
Post-History:
05-May-2008, 05-Jun-2008

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 Python 3000을 위한 새로운 문자열 표현 형식을 제안합니다. Python 3000 이전의 Python에서 repr() 내장 함수는 디버깅과 로깅을 위해 임의의 객체를 출력 가능한 ASCII 문자열로 변환했습니다. Python 3000에서는 유니코드 표준에 기반한 더 넓은 범위의 문자를 ‘출력 가능’한 것으로 간주해야 합니다.

동기

현재의 repr()은 다음 알고리즘을 사용하여 8비트 문자열을 ASCII로 변환합니다.

  • CR, LF, TAB, ‘\’를 각각 ‘\r’, ‘\n’, ‘\t’, ‘\\’로 변환합니다.
  • 그 밖의 출력 불가능한 문자(0x00-0x1f, 0x7f)와 비ASCII 문자(>= 0x80)를 ‘\xXX’로 변환합니다.
  • 따옴표 문자(아포스트로피, ‘)를 백슬래시로 이스케이프하고, 맨 앞과 맨 뒤에 따옴표 문자를 추가합니다.

유니코드 문자열의 경우, 다음과 같은 변환이 추가로 이루어집니다.

  • 뒤따르는 문자가 없는 선행 서로게이트 쌍 문자(0xd800-0xdbff이지만 뒤에 0xdc00-0xdfff가 오지 않는 경우)를 ‘\uXXXX’로 변환합니다.
  • 16비트 문자(>= 0x100)를 ‘\uXXXX’로 변환합니다.
  • 21비트 문자(>= 0x10000)와 서로게이트 쌍 문자를 ‘\U00xxxxxx’로 변환합니다.

이 알고리즘은 모든 문자열을 출력 가능한 ASCII로 변환하므로, repr()은 디버깅이나 로깅을 위해 문자열을 출력하는 편리하고 안전한 방법으로 사용됩니다. 모든 비ASCII 문자가 이스케이프되지만, 문자열의 대부분이 ASCII 문자일 때는 이것이 문제가 되지 않습니다. 그러나 일본어처럼 문자열의 문자 대부분이 ASCII가 아닌 다른 언어에서는 이것이 매우 불편합니다.

print(aJapaneseString)를 사용하면 읽을 수 있는 문자열을 얻을 수 있지만, 리스트나 튜플 같은 컬렉션에 담긴 문자열을 출력할 때는 이와 비슷한 우회 방법이 없습니다. print(listOfJapaneseStrings)는 출력할 문자열을 만들 때 repr()을 사용하므로, 결과 문자열은 항상 16진수로 이스케이프됩니다. 또는 open(japaneseFilename)이 예외를 일으키면, 오류 메시지가 IOError: [Errno 2] No such file or directory: '\u65e5\u672c\u8a9e'와 같은 형태가 되어 도움이 되지 않습니다.

Python 3000에는 비ASCII 식별자처럼 비라틴 문자 사용자를 위한 좋은 기능이 많으므로, Python이 출력 가능한 결과에 대해서도 비슷한 방향으로 발전할 수 있다면 도움이 될 것입니다.

일부 사용자는 이미지 같은 바이너리 데이터를 출력하면 이러한 출력 때문에 콘솔이 엉망이 될까 걱정할 수도 있습니다. 그러나 Python 3000에서는 바이트와 문자열이 서로 다른 타입이므로 실제로 그런 일이 일어날 가능성은 낮으며, 이미지를 콘솔에 출력해도 콘솔이 엉망이 되지 않습니다.

이 문제는 Hye-Shik Chang [1]이 한 차례 논의한 적이 있으나, 기각되었습니다.

명세

  • Python C API에 새로운 함수 int Py_UNICODE_ISPRINTABLE (Py_UNICODE ch)를 추가합니다. 이 함수는 repr()이 유니코드 문자 ch를 이스케이프해야 하면 0을 반환하고, 그렇지 않으면 1을 반환합니다. 이스케이프해야 하는 문자는 유니코드 문자 데이터베이스에서 다음과 같이 정의된 문자입니다.
    • Cc (Other, Control)
    • Cf (Other, Format)
    • Cs (Other, Surrogate)
    • Co (Other, Private Use)
    • Cn (Other, Not Assigned)
    • Zl (구분자, 줄)은 LINE SEPARATOR (’\u2028’)를 가리킵니다.
    • Zp (구분자, 문단)는 PARAGRAPH SEPARATOR (’\u2029’)를 가리킵니다.
    • ASCII 공백(’\x20’)을 제외한 Zs (구분자, 공백)입니다. 이 범주에 속하는 문자는 모호함을 피하기 위해 이스케이프해야 합니다.
  • repr() 문자열을 만드는 알고리즘은 다음과 같이 변경되어야 합니다.
    • CR, LF, TAB, ‘\’를 각각 ‘\r’, ‘\n’, ‘\t’, ‘\\’로 변환합니다.
    • 출력할 수 없는 ASCII 문자(0x00-0x1f, 0x7f)를 ‘\xXX’로 변환합니다.
    • 뒤따르는 문자가 없는 서로게이트 쌍의 선행 문자(0xd800-0xdbff 중 뒤에 0xdc00-0xdfff가 오지 않는 것)를 ‘\uXXXX’로 변환합니다.
    • 출력할 수 없는 문자(Py_UNICODE_ISPRINTABLE()이 0을 반환하는 문자)를 ‘\xXX’, ‘\uXXXX’ 또는 ‘\U00xxxxxx’로 변환합니다.
    • 따옴표 문자(아포스트로피, 0x27)를 백슬래시로 이스케이프하고, 시작과 끝에 따옴표 문자를 추가합니다.
  • sys.stderr의 유니코드 오류 핸들러를 기본적으로 ‘backslashreplace’로 설정합니다.
  • Python C API에 새 함수 PyObject *PyObject_ASCII (PyObject *o)를 추가합니다. 이 함수는 PyObject_Repr()을 사용해 모든 Python 객체를 문자열로 변환한 다음, ASCII가 아닌 모든 문자를 16진수 이스케이프합니다. PyObject_ASCII()는 Python 2의 PyObject_Repr()와 같은 문자열을 생성합니다.
  • 새 내장 함수 ascii()를 추가합니다. 이 함수는 repr()을 사용해 모든 Python 객체를 문자열로 변환한 다음, ASCII가 아닌 모든 문자를 16진수 이스케이프합니다. ascii()는 Python 2의 repr()와 같은 문자열을 생성합니다.
  • '%a' 문자열 형식 연산자를 추가합니다. '%a'는 repr()을 사용해 모든 Python 객체를 문자열로 변환한 다음, ASCII가 아닌 모든 문자를 16진수 이스케이프합니다. '%a' 포맷 연산자는 Python 2의 '%r'이 생성하는 것과 같은 문자열을 생성합니다. 또한 string.format() 메서드에 '!a' 변환 플래그를 추가하고, PyUnicode_FromFormat()에 '%A' 연산자를 추가합니다. 이들은 '%a' 문자열 포맷 연산자와 마찬가지로 임의의 객체를 ASCII 문자열로 변환합니다.
  • 문자열 타입에 isprintable() 메서드를 추가합니다. str.isprintable()은 repr()이 문자열의 문자를 하나라도 이스케이프하게 되면 False를 반환하고, 그렇지 않으면 True를 반환합니다. isprintable() 메서드는 내부적으로 Py_UNICODE_ISPRINTABLE() 함수를 호출합니다.

근거

Python 3000의 repr()은 Python 3000의 문자열과 마찬가지로 ASCII가 아니라 유니코드를 기반으로 해야 합니다. 또한 로케일이 반드시 출력 장치의 로케일과 같다는 보장이 없으므로, 변환은 로케일 설정의 영향을 받아서는 안 됩니다. 예를 들어, 데몬 프로세스가 ASCII 설정에서 실행되지만 로그 파일에는 UTF-8로 기록하는 경우는 흔합니다. 또한 웹 애플리케이션은 HTML 페이지의 인코딩에 따라 오류 정보를 더 읽기 쉬운 형태로 보고하고자 할 수 있습니다.

사용자의 콘솔이 지원하지 않는 문자는 출력할 때 유니코드 인코더의 오류 핸들러에 의해 16진수 이스케이프될 수 있습니다. 출력 파일의 오류 핸들러가 ‘backslashreplace’이면, 이러한 문자는 UnicodeEncodeError를 발생시키지 않고 16진수 이스케이프됩니다. 예를 들어, 기본 인코딩이 ASCII이면 print('Hello ¢')는 ‘Hello \xa2’를 출력합니다. 인코딩이 ISO-8859-1이면 ‘Hello ¢’가 출력됩니다.

sys.stdout의 기본 오류 핸들러는 ‘strict’입니다. 출력을 읽는 다른 애플리케이션이 16진수로 이스케이프된 문자를 이해하지 못할 수도 있으므로, 지원되지 않는 문자는 쓸 때 잡아내야 합니다. 지원되지 않는 문자를 반드시 이스케이프해야 한다면, 오류 핸들러를 명시적으로 변경해야 합니다. sys.stdout과 달리 sys.stderr는 기본 오류 핸들러가 ‘backslashreplace’이므로 기본적으로 UnicodeEncodingError를 발생시키지 않습니다. 따라서 비ASCII 문자가 포함된 오류 메시지를 sys.stderr에 출력해도 예외가 발생하지 않습니다. 또한 처리되지 않은 예외에 관한 정보(예외 객체, 트레이스백)는 인터프리터가 예외를 발생시키지 않고 출력합니다.

대안

repr()을 변경하지 않고 비라틴 문자 언어에서의 디버깅을 돕기 위해 다른 제안들이 있었습니다.

  • 리스트나 딕셔너리를 출력하는 도구를 제공합니다.

    디버깅을 위해 출력할 문자열은 리스트나 딕셔너리에만 들어 있는 것이 아니라 다른 여러 유형의 객체에도 들어 있습니다. 파일 객체에는 유니코드로 된 파일 이름이 들어 있고, 예외 객체에는 유니코드로 된 메시지가 들어 있는 식입니다. 이러한 문자열은 repr()을 적용했을 때 읽을 수 있는 형태로 출력되어야 합니다. 가능한 모든 객체 유형을 출력하는 도구를 구현하기는 어려울 것입니다.

  • sys.displayhook과 sys.excepthook을 사용합니다.

    대화형 세션에서는 16진수로 이스케이프된 문자를 원래 문자로 복원하는 훅을 작성할 수 있습니다. 하지만 이러한 훅은 대화형 Python 세션에서 입력한 표현식을 평가한 결과를 출력할 때만 호출되며, print() 함수, 비대화형 세션, logging.debug("%r", ...) 등에서는 동작하지 않습니다.

  • sys.stdout과 sys.stderr의 서브클래스를 만듭니다.

    문자열이 된 시점에는 모든 경우에 이스케이프를 올바르게 되돌리기에 충분한 정보가 남아 있지 않기 때문에, 16진수로 이스케이프된 문자를 복원하는 서브클래스를 구현하기는 어렵습니다. 예를 들어 print("\\"+"u0041")는 ‘A’가 아니라 ‘\u0041’로 출력되어야 합니다. 하지만 파일 객체로서는 이 둘을 구별할 방법이 없습니다.

  • unicode_repr()이 사용하는 인코딩을 조정할 수 있게 하고, 기존 repr()을 기본값으로 합니다.

    repr()을 조정할 수 있으면 repr()을 사용한 결과를 예측할 수 없게 되어, repr()이 관련된 올바른 코드를 작성하는 것이 불가능해집니다. 그리고 현재의 repr()이 기본값이라면 기존 관례가 그대로 유지되므로, 사용자는 repr()의 결과로 ASCII 문자열을 기대할 수 있습니다. 사용자 정의 repr() 함수가 사용되면 서드파티 애플리케이션이나 라이브러리가 혼란을 겪을 수 있습니다.

하위 호환성

repr()을 변경하면 일부 기존 코드, 특히 테스트 코드가 동작하지 않을 수 있습니다. 이 수정으로 Python의 회귀 테스트 중 다섯 개가 실패합니다. Python 2에서처럼 비ASCII 문자가 없는 repr() 문자열이 필요하다면 다음 함수를 사용할 수 있습니다.

def repr_ascii(obj):
    return str(repr(obj).encode("ASCII", "backslashreplace"), "ASCII")

로깅이나 디버깅을 할 때 다음 코드는 UnicodeEncodeError를 발생시킬 수 있습니다.

log = open("logfile", "w")
log.write(repr(data))     # UnicodeEncodeError will be raised
                          # if data contains unsupported characters.

예외가 발생하지 않도록 하려면 오류 핸들러를 명시적으로 지정할 수 있습니다.

log = open("logfile", "w", errors="backslashreplace")
log.write(repr(data))  # Unsupported characters will be escaped.

유니코드 기반 인코딩(예: en_US.utf8 또는 de_DE.utf8)을 사용하는 콘솔에서는 backslashreplace 기법이 작동하지 않으며, 출력 가능한 문자는 모두 이스케이프되지 않습니다. 이 때문에 서유럽어, 그리스어, 키릴 문자 언어에서 모양이 비슷한 문자들이 문제를 일으킵니다. 이 언어들은 공통 조상에서 갈라져 나온, 비슷하지만 서로 다른 알파벳을 사용하므로 모양은 비슷하지만 문자 코드가 다른 글자들을 포함합니다. 예를 들어, 라틴 문자 ‘a’, ‘e’, ‘o’를 키릴 문자 ‘а’, ‘е’, ‘о’와 구별하기는 어렵습니다. (물론 실제로 어떻게 보이는지는 사용하는 글꼴에 따라 크게 달라지지만, 대개 이 글자들은 거의 구별할 수 없습니다.) 이 문제를 피하려면 사용자가 터미널 인코딩을 조정하여 자신의 환경에 맞는 결과를 얻을 수 있습니다.

거부된 제안

  • 내장 print() 함수에 인코딩 인자(encoding)와 errors 인자를 추가하며, 기본값은 각각 sys.getfilesystemencoding()과 ‘backslashreplace’입니다.

    구현이 복잡하며, 일반적으로 좋은 아이디어로 여겨지지 않습니다. [2]

  • 16진수 문자 코드 대신 문자 이름을 사용하여 문자를 이스케이프합니다. 예를 들어, repr('\u03b1')는 "\N{GREEK SMALL LETTER ALPHA}"로 변환될 수 있습니다.

    문자 이름을 사용하면 16진수 이스케이프에 비해 매우 장황해질 수 있습니다. 예를 들어, repr("\ufbf9")는 "\N{ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM}"로 변환됩니다.

  • sys.stdout의 기본 오류 핸들러는 ‘backslashreplace’여야 합니다.

    stdout에 기록된 내용을 다른 프로그램이 받아서 처리할 수 있는데, 그 프로그램이 \ 이스케이프를 잘못 해석할 수 있습니다. 대화형 세션에서는 ‘backslashreplace’ 오류 핸들러를 기본값으로 만들 수 있지만, 이는 “대화형 모드에서는 동작하지만 파일로 리디렉션하면 동작하지 않는다”와 같은 종류의 혼란을 더할 수 있습니다.

구현

저자는 http://bugs.python.org/issue2630 에 패치를 작성했으며, 이 패치는 2008년 6월 11일에 리비전 64138로 Python 3.0 브랜치에 커밋되었습니다.

참고 문헌