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
번역·라이선스 안내
이 비공식 한국어 번역은 원문 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 (기타, 제어)
- Cf (기타, 형식)
- Cs (기타, 서로게이트)
- Co (기타, 사적 사용)
- Cn (기타, 미할당)
- Zl (구분자, 줄)은 줄 구분자(’\u2028’)를 나타냅니다.
- Zp (구분자, 단락)은 단락 구분자(’\u2029’)를 나타냅니다.
- Zs (구분자, 공백)은 ASCII 공백(’\x20’) 이외의 공백을 나타냅니다. 이 범주의 문자는 모호성을 피하기 위해 이스케이프해야 합니다.
- 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'과 동일한 문자열을 생성합니다. 또한'!a'변환 플래그를string.format()메서드에 추가하고 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는 기본적으로 UnicodeEncodingError를 발생시키지 않습니다. 기본 오류 처리기가 ‘backslashreplace’이기 때문입니다. 따라서 비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의 회귀 테스트 5개가 실패합니다. 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’와 키릴 문자 ‘а’, ‘е’, ‘о’를 구별하기는 어렵습니다. (물론 시각적 표현은 사용된 글꼴에 크게 좌우되지만, 일반적으로 이러한 문자들은 거의 구별할 수 없습니다.) 이 문제를 피하려면 사용자가 터미널 인코딩을 조정하여 자신의 환경에 적합한 결과를 얻을 수 있습니다.
거부된 제안
- 인코딩 및 errors 인자를 내장 print() 함수에 추가하고, 기본값을 각각 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 브랜치에 커밋되었습니다.
참고 자료
Copyright
This document has been placed in the public domain.