PEP 414 – Python 3.3을 위한 명시적 유니코드 리터럴
- Author:
- Armin Ronacher <armin.ronacher at active-4.com>, Alyssa Coghlan <ncoghlan at gmail.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 15-Feb-2012
- Python-Version:
- 3.3
- Post-History:
- 28-Feb-2012, 04-Mar-2012
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 문서는 유니코드를 인식하는 Python 2 애플리케이션을 Python 3로 이식할 때 필요한 변경의 양을 줄이기 위해, Python 2.x의 명시적 유니코드 리터럴을 Python 3.x 언어 명세에 다시 통합할 것을 제안합니다.
BDFL 선언
이 PEP는 Python 3.3에 대해 공식적으로 수락되었습니다:
이 PEP를 수락합니다. 이보다 더 무해한 제안은 찾기 어려울 정도입니다. 그렇게 하십시오.
제안
이 PEP는 Python 3.3에서 Python 2의 유니코드 리터럴 문법 지원을 복원할 것을 제안합니다. 이를 통해 유니코드를 인식하는 애플리케이션에서 수정 없이 Python 3에서 실행될 기존 Python 2 코드의 줄 수가 크게 늘어납니다.
구체적으로, 문자열 리터럴 접두사에 대한 Python 3의 정의는 다음을 허용하도록 확장됩니다:
"u" | "U"
현재 지원되는 다음 접두사에 더해:
"r" | "R"
다음은 모두 일반적인 Python 3 문자열을 나타냅니다:
'text'
"text"
'''text'''
"""text"""
u'text'
u"text"
u'''text'''
u"""text"""
U'text'
U"text"
U'''text'''
U"""text"""
Python 3의 실제 유니코드 처리에 대해서는 어떠한 변경도 제안하지 않으며, 문자열 리터럴로 허용되는 형식만 변경할 것을 제안합니다.
“Raw” 유니코드 리터럴의 제외
Python 2는 raw 문자열의 통상적인 정의에 부합하지 않는 “raw” 유니코드 리터럴이라는 개념을 지원합니다. \uXXXX와 \UXXXXXXXX 이스케이프 시퀀스는 여전히 컴파일러가 처리하며, 연관된 유니코드 객체를 생성할 때 적절한 유니코드 코드 포인트로 변환됩니다.
Python 3에는 이에 대응하는 개념이 없습니다. 컴파일러는 raw 문자열 리터럴의 내용에 대해 어떠한 전처리도 수행하지 않습니다. 이는 Python 2의 8비트 raw 문자열 리터럴의 동작과 일치합니다.
이러한 문자열은 거의 사용되지 않으며, 허용하더라도 Python 3에서는 다르게 해석될 것이므로, 아예 제외하는 것이 더 나은 선택이라고 결정했습니다. 따라서 이러한 리터럴을 사용하는 코드는 잠재적으로 다른 출력을 생성하는 대신 Python 3에서 여전히 (문법 오류(Syntax Error)와 함께) 즉시 실패합니다.
Python 2와 Python 3 양쪽에서 실행되는 동등한 동작을 얻으려면, 일반 유니코드 리터럴을 (문자열 안에서 적절한 추가 이스케이프와 함께) 사용하거나, 문자열 연결 또는 문자열 포매팅을 사용하여 문자열의 raw 부분과 유니코드 이스케이프 시퀀스를 사용해야 하는 부분을 결합할 수 있습니다.
Python 2에서 from __future__ import unicode_literals를 사용하면, 명목상 “raw”인 유니코드 문자열 리터럴은 “raw Unicode” 접두사로 명시적으로 표시된 Python 2 문자열과 마찬가지로 \uXXXX와 \UXXXXXXXX 이스케이프 시퀀스를 처리한다는 점에 유의하십시오.
저자의 말
이 PEP는 원래 Armin Ronacher가 작성했으며, Guido의 승인은 그 버전을 기반으로 이루어졌습니다.
현재 게시된 버전은 Alyssa Coghlan이 다시 작성한 것으로, Guido가 결정을 내릴 때 고려했지만 Armin 버전의 PEP에는 명시적으로 문서화되지 않았던 추가적인 역사적 세부 사항과 근거를 포함합니다.
독자는 이 PEP에서 제시하는 인자(논거) 중 상당수가 기술적인 것이 아니라는 점을 알아 두어야 합니다. 오히려 이 논거들은 소프트웨어 개발의 사회적 측면과 개인적 측면에 깊이 관련되어 있습니다.
근거
Python 3.2용으로 Python 3과 호환되는 버전의 웹 서비스 게이트웨이 인터페이스(WSGI) 명세(PEP 3333)가 릴리스되면서, Python 웹 생태계의 많은 부분이 기존 개발자 및 사용자 커뮤니티에 악영향을 주지 않으면서 Python 3을 지원하기 위해 공동의 노력을 기울여 왔습니다.
Chris McDonough(WebOb, Pyramid), Armin Ronacher(Flask, Werkzeug), Jacob Kaplan-Moss(Django), Kenneth Reitz(requests)를 비롯한 해당 커뮤니티 핵심 개발자들로부터 받은 주요 피드백 중 하나는, (어떤 방식으로 하든) 애플리케이션의 모든 유니코드 리터럴 표기를 바꿔야 한다는 요구 사항이 이식 작업의 핵심 걸림돌이라는 것입니다.
특히 다른 많은 Python 3 변경 사항과 달리, 이는 프레임워크와 라이브러리 작성자가 사용자를 대신하여 쉽게 처리할 수 있는 것이 아닙니다. 그러한 사용자 대부분은 Python 언어 명세의 “순수성”에는 전혀 관심이 없으며, 그저 자신의 웹사이트와 애플리케이션이 가능한 한 잘 동작하기를 원할 뿐입니다.
이 우려를 가장 적극적으로 제기해 온 곳은 Python 웹 커뮤니티이지만, 유니코드를 많이 다루는 다른 분야(GUI 개발 등)도 해당 분야와 그 커뮤니티가 Python 3 지원에 본격적으로 나서기 시작하면 비슷한 문제에 부딪힐 것으로 예상됩니다.
흔한 반론
불만: 이 PEP는 Python 3.2의 채택을 저해할 수 있습니다
이 불만은 흥미롭습니다. 이 PEP가 유니코드를 다루는 Python 2 애플리케이션을 Python 3으로 포팅하는 일을 실제로 더 쉽게 만들어 줄 것이라는 암묵적인 인정이 그 안에 담겨 있기 때문입니다.
기존 포팅 도구 모음이 부과하는 제약을 감수하거나, 문제가 최소화될 만큼 자신들의 Python 2 코드베이스를 충분히 수정할 준비가 되어 있는 Python 커뮤니티는 이미 많이 있습니다.
이 PEP는 그러한 커뮤니티를 위한 것이 아닙니다. 오히려 그러한 어려움을 감수하고 싶지 않은 사람들을 돕기 위해 특별히 설계되었습니다.
그러나 이 제안은 의미 변경이 없는 비교적 작은 언어 문법 조정에 관한 것이므로, 서드파티 임포트 훅으로 지원하는 것이 가능합니다. 이러한 임포트 훅은 임포트 시점에 어느 정도의 오버헤드를 발생시키고, 훅이 필요한 각 애플리케이션이 이를 설치하기 위한 추가 단계를 거쳐야 하지만, 유니코드 리터럴 접두사를 사용하는 탓에 원래는 Python 3.3 이상에서만 실행될 라이브러리와 프레임워크를 Python 3.2를 대상으로 하는 애플리케이션에서도 사용할 수 있게 해 줍니다.
그러한 임포트 훅 프로젝트 중 하나가 Vinay Sajip의 uprefix [4]입니다.
임포트 시점에 즉석에서 변환하기보다 코드를 미리 변환하는 쪽을 선호하는 사람들을 위해, Armin Ronacher가 임포트 중이 아니라 설치 시점에 실행되는 훅을 개발하고 있습니다 [5].
물론 두 접근 방식을 결합하는 것도 가능합니다. 예를 들어, 로컬 개발 중의 빠른 편집-테스트 주기에는 임포트 훅을 사용하고, 지속적 통합 작업과 Python 3.2 환경으로의 배포에는 설치 훅을 사용할 수 있습니다.
이 절에서 설명한 접근 방식은, 예를 들어 Python 2.7과 3.2를 공식 지원 Python 버전으로 탑재하여 출시될 Ubuntu 12.04 LTS 릴리스에서 Python 3을 대상으로 하려는 애플리케이션에 유용할 수 있습니다.
불만: Python 2로부터의 포팅을 지원하기 위해서만 Python 3을 더 나쁘게 만들어서는 안 됩니다
이는 실제로 Python 3의 핵심 설계 원칙 중 하나입니다. 그러나 Python 전체의 핵심 설계 원칙 중 하나는 “실용성이 순수성보다 우선합니다(practicality beats purity)”라는 것입니다. 서드파티 개발자에게 상당한 부담을 지우려 한다면, 그렇게 하는 데 대한 확실한 근거가 있어야 합니다.
대부분의 경우, 하위 호환성을 깨는 Python 3 변경의 근거는 코드의 정확성을 높이거나(예를 들어, 바이너리 데이터와 텍스트 데이터의 더 엄격한 기본 분리, 그리고 필요할 때 부동소수점으로 승격되는 정수 나눗셈), 일반적인 메모리 사용량을 줄이거나(예를 들어, 구체적인 리스트 대신 이터레이터와 뷰의 사용 증가), 표현력을 높이지 않으면서 Python 코드를 읽기 어렵게 만드는 거슬리는 요소를 제거하는 것(예를 들어, 잡은 예외에 이름을 붙이는 쉼표 기반 문법)입니다. 이러한 근거에 기반한 변경은 Python 3으로 전환하려는 Python 2 개발자들의 반대와 관계없이 결코 되돌려지지 않을 것입니다.
많은 경우 Python 2는 역사적인 이유로 같은 일을 하는 두 가지 방법을 제공했습니다. 예를 들어, 부등 비교는 !=와 <>를 모두 사용하여 할 수 있었고, 정수 리터럴에는 선택적으로 L 접미사를 붙일 수 있었습니다. 이러한 중복은 Python 3에서 제거되었으며, 그 결과 언어의 전체 규모가 줄어들고 개발자 간의 일관성이 향상되었습니다.
원래의 Python 3 설계(Python 3.2까지 포함)에서는 유니코드 리터럴을 위한 명시적 접두사 문법이 Python 3에서 전혀 필요하지 않으므로 이 범주에 속한다고 간주되었습니다. 그러나 다른 경우들과 유니코드 리터럴의 차이는, 유니코드 리터럴 접두사가 Python 2 코드에서는 중복이 아니라는 점입니다. 이는 프로그램상 의미 있는 구분이며, 정보 손실을 피하려면 어떤 방식으로든 보존되어야 합니다.
전환을 돕기 위한 포팅 도구들이 만들어지기는 했지만(다음 절 참조), 이는 여전히 Python 2에서 유니코드 문자열을 많이 사용하는 사람들에게 추가적인 부담을 지웁니다. 그것도 오로지 앞으로 Python 3을 배우는 개발자들이 “역사적인 이유로 문자열 리터럴에는 선택적으로 u 또는 U 접두사를 붙일 수 있습니다. 여러분은 절대 사용하지 마십시오. 이전 버전의 언어에서 포팅하는 것을 돕기 위해 있을 뿐입니다.”라는 말을 듣지 않아도 되게 하기 위해서입니다.
Python 2를 배운 많은 학생이 문자열 예외에 관해 비슷한 경고를 들었지만, 혼란을 겪거나 Python 개발자로서의 성장이 회복할 수 없을 만큼 저해되지는 않았습니다. 이 기능도 마찬가지일 것입니다.
이 점은 Python 3이 바이트 리터럴과 원시(raw) 바이트 및 문자열 리터럴에 대해 B와 R 접두사의 대문자 변형을 여전히 허용한다는 사실로 더욱 뒷받침됩니다. 문자열 접두사 변형으로 인한 혼란의 가능성이 그토록 심각하다면, Python 3에서 제거된 다른 모든 중복과 함께 이러한 중복 접두사도 제거하라고 요구하는 항의의 목소리는 어디에 있었습니까?
문자열 예외 지원이 일반적인 폐지(deprecation) 절차를 거쳐 Python 2에서 제거되었듯이, 중복되는 문자열 접두사 문자(구체적으로 B, R, u, U) 지원도 현재 이 PEP가 수용되는지와 관계없이 결국 Python 3에서 제거될 수 있습니다. 하지만 그러한 변경은 Python 2.7을 지원하는 서드파티 라이브러리가 오늘날 Python 2.2나 2.3을 지원하는 라이브러리만큼만 흔해진 뒤에야 일어날 가능성이 높습니다.
불만: WSGI의 “네이티브 문자열” 개념은 보기 흉한 편법입니다
유니코드 리터럴 제거가 웹 개발 커뮤니티에서 이처럼 큰 우려를 불러일으킨 이유 중 하나는, 개정된 WSGI 명세가 WSGI 호환 인터페이스를 제공하는 기존 웹 서버에 미치는 혼란을 최소화하기 위해 몇 가지 타협을 해야 했기 때문입니다(개정된 표준이 웹 애플리케이션 작성자와 웹 프레임워크 개발자에게 실현 가능한 목표가 되려면 이것이 필요하다고 판단되었습니다).
그러한 타협 중 하나가 “네이티브 문자열”이라는 개념입니다. WSGI는 세 가지 종류의 문자열을 정의합니다:
- 텍스트 문자열: Python 2에서는
unicode로, Python 3에서는str로 처리됩니다 - 네이티브 문자열: Python 2와 Python 3 모두에서
str로 처리됩니다 - 바이너리 데이터: Python 2에서는
str로, Python 3에서는bytes로 처리됩니다
일부 개발자는 WSGI의 “네이티브 문자열”을 보기 흉한 편법으로 여깁니다. 기반 데이터의 실제 인코딩과 관계없이 latin-1로 디코딩된 “텍스트”에만 사용된다고 명시적으로 문서화되어 있기 때문입니다. 이 접근 방식을 사용하면 텍스트 인코딩의 올바른 처리를 장려하도록 설계된 Python 3 데이터 모델의 여러 개선 사항을 우회하게 됩니다. 하지만 문제 영역의 특수한 사정 덕분에 대체로 잘 동작합니다. 웹 서버와 웹 프레임워크 개발자는 HTTP 및 관련 프로토콜을 다룰 때 바이너리 데이터와 텍스트의 경계가 얼마나 모호해질 수 있는지, 그리고 인코딩된 텍스트 데이터를 조작할 때 사용 중인 인코딩이 갖는 의미를 이해하는 것이 얼마나 중요한지를 가장 잘 알고 있는 사람들에 속합니다. 애플리케이션 수준에서는 이러한 세부 사항 대부분이 웹 프레임워크와 지원 라이브러리에 의해 개발자에게 감춰집니다(Python 2와 Python 3 모두에서).
실제로 네이티브 문자열은 유용한 개념입니다. 주로 str을 다루도록 설계된 API(표준 라이브러리와 서드파티 프레임워크 및 패키지 모두에 있는)와 인터프리터 내부 세부 사항이 일부 존재하기 때문입니다. 이러한 구성 요소는 흔히 Python 2의 unicode나 Python 3의 bytes를 지원하지 않으며, 지원하더라도 추가적인 인코딩 정보를 요구하거나 str 변형에는 적용되지 않는 제약을 부과합니다.
실제 str 인스턴스를 사용하여 처리하는 것이 가장 좋은 인터페이스의 예는 다음과 같습니다:
- Python 식별자(속성, 딕셔너리 키, 클래스 이름, 모듈 이름, import 참조 등으로 사용되는 것)
- 대부분의 URL과 urllib/http 서버의 HTTP 헤더
- WSGI 환경 키와 CGI에서 물려받은 값
- 동적 컴파일과 AST 조작을 위한 Python 소스 코드
- 예외 메시지
__repr__반환값- 선호되는 파일 시스템 경로
- 선호되는 OS 환경
Python 2.6과 2.7에서는 이러한 구분을 다음과 같이 표현하는 것이 가장 자연스럽습니다:
u"": 텍스트 문자열 (unicode)"": 네이티브 문자열 (str)b"": 바이너리 데이터 (str,bytes라는 별칭도 있음)
Python 3에서는 latin-1로 디코딩된 네이티브 문자열이 다른 텍스트 문자열과 구별되지 않습니다:
"": 텍스트 문자열 (str)"": 네이티브 문자열 (str)b"": 바이너리 데이터 (bytes)
from __future__ import unicode_literals를 사용하여 Python 2의 동작을 바꾼다면, n()을 적절히 정의하여 다음과 같이 구분을 표현할 수 있습니다:
"": 텍스트 문자열n(""): 네이티브 문자열b"": 바이너리 데이터
(n=str은 간단한 경우에는 동작하지만, ASCII가 아닌 소스 인코딩 때문에 문제가 생길 때가 있습니다)
Python 2와 Python 3의 공통 부분 집합에서는 (소스 인코딩에 대한 적절한 명세와 u() 및 b() 헬퍼 함수의 정의가 있다면) 다음과 같이 표현할 수 있습니다:
u(""): 텍스트 문자열"": 네이티브 문자열b(""): 바이너리 데이터
마지막 방식은 Python 2.5 이하를 지원하는 유일한 방식입니다.
모든 대안 가운데 현재 Python 2.6과 2.7에서 지원하는 형식이 원하는 세 가지 동작을 명확하게 구분하는, 단연 가장 깔끔한 방식입니다. 이 PEP에 따라 이 형식은 Python 3.3 이상에서도 지원됩니다. 또한 임포트 훅과 설치 훅을 사용하면 Python 3.1과 3.2에서도 지원됩니다. 가능성은 훨씬 낮지만, 이 훅을 조정하여 Python 2.5에서 b 접두사를 사용할 수 있게 하는 것도 생각해 볼 수 있습니다.
불만: 기존 도구로도 누구에게나 충분할 것입니다
이미 애플리케이션을 Python 3으로 성공적으로 포팅한 개발자들은 흔히 “어렵다고 생각한다면 잘못하고 있는 것입니다”나 “그렇게 어렵지 않으니 일단 해 보십시오!”와 같은 의견을 내놓습니다. 분명 의도한 것은 아니겠지만, 이러한 반응은 모두 현재 포팅 도구 모음의 부족한 점을 지적하는 사람들에게 “포팅 도구에는 아무 문제가 없습니다. 당신이 서툴러서 도구를 제대로 쓸 줄 모르는 것뿐입니다”라고 말하는 것과 같은 효과를 냅니다.
이러한 반응은 사람들이 무엇에 불만을 제기하는지 그 요점을 완전히 놓치고 있습니다. 이 PEP로 이어진 피드백은 포팅이 불가능하다는 불만에서 나온 것이 아닙니다. 오히려 이 피드백은 포팅을 성공적으로 완료한 사람들에게서 나온 것으로, 이들은 자신이 포팅해야 했던 종류의 애플리케이션(구체적으로는 유니코드를 인식하는 웹 프레임워크와 지원 라이브러리)에서는 그 경험이 매우 불쾌했다고 이의를 제기하고 있습니다.
이는 주관적인 평가이며, 바로 이 때문에 Python 3 포팅 도구 생태계는 “명백한 한 가지 방법” 철학이 결코 적용되지 않는 사례입니다. 원래는 “Python 2로 개발하고, 2to3로 변환하고, 둘 다 테스트한다”가 두 버전을 병행하여 개발하는 표준 방식이 되도록 의도했지만, 실제로는 프로젝트와 개발자 커뮤니티마다 요구가 상당히 다양하다는 것이 드러났고, 그 결과 다양한 방식이 고안되어 각 그룹이 자신의 요구에 가장 잘 맞는 방식을 선택할 수 있게 되었습니다.
Lennart Regebro는 사용 가능한 마이그레이션 전략에 대한 훌륭한 개요를 작성했으며 [2], 공식 포팅 가이드 [3]에서도 비슷한 검토를 제공합니다. (Lennart가 개요를 작성한 이후 공식 지침은 “구체적인 상황에 따라 다릅니다”로 완화되었다는 점에 유의하십시오).
그러나 두 가이드 모두 관련된 모든 개발자가 이미 Python 3 지원이라는 생각에 전념하고 있다는 기본 가정에서 작성되었습니다. 이 가이드들은 명확한 이점이 없는 혼란을 그다지 용인하지 않을 수도 있는 사용자층을 상대하거나, Python 2에 집중하는 업스트림 개발자에게 오로지 Python 3 상위 호환성 향상만을 위한 패치를 받아들이도록 설득하려 할 때 그러한 변화가 가지는 사회적 측면을 전혀 고려하지 않습니다.
현재 포팅 도구 모음으로는 모든 마이그레이션 전략이 프로젝트의 모든 유니코드 리터럴을 변경하게 됩니다. 예외는 없습니다. 이 리터럴들은 접두사 없는 문자열 리터럴로 변환되거나(프로젝트가 unicode_literals 임포트를 채택하기로 한 경우), 그렇지 않으면 u("text")와 같은 변환 함수 호출로 변환됩니다.
unicode_literals 임포트 방식을 사용하되 프로젝트 전체에 동시에 채택하지 않으면, 접두사 없는 문자열 리터럴의 의미가 성가실 정도로 모호해질 수 있습니다. 이 문제는 Django 사이트처럼 여러 구성 요소를 모아 만든 소프트웨어에서 특히 해로울 수 있습니다. 이런 상황에서는 일부 파일은 unicode_literals 임포트를 사용하고 다른 파일은 사용하지 않게 되어 혼란이 생길 여지가 분명히 있습니다.
이러한 문제는 기술적으로는 분명히 해결할 수 있지만, 사회적으로는 전혀 불필요하게 주의를 흩뜨리는 요소입니다. 개발자의 에너지는 Python 3 전환과 관련된 실제 기술적 어려움(예를 들어 8비트 텍스트 문자열과 바이너리 데이터를 구분하는 일)을 해결하는 데 써야 합니다. 개발자들이 Python 2에서 유니코드 문자열을 이미 명시적으로 구분해 두었다는 이유만으로, 추가적인 코드 변경(자동화된 변경이라 하더라도)이라는 불이익을 받아서는 안 됩니다.
Armin Ronacher는 2to3의 실험적 확장을 만들었습니다. 이 확장은 버전 간 호환성 라이브러리인 six의 도움을 받아 Python 2.7 이상에서 실행될 정도까지만 Python 코드를 현대화합니다. 이 도구는 python-modernize [1]라는 이름으로 제공됩니다. 현재 이 도구가 만들어 내는 변경분은 변환된 소스의 모든 유니코드 리터럴에 영향을 줍니다. 이렇게 되면 그러한 변경을 받아들이도록 요청받는 업스트림 개발자들과 자신의 애플리케이션을 변경하도록 요청받는 프레임워크 사용자들이 정당한 우려를 제기할 것입니다.
그러나 유니코드 리터럴 문법 변경으로 인한 잡음을 없애면, 많은 프로젝트가 python-modernize를 실행하고 권장 변경 사항을 적용하는 것만으로 깔끔하게, 그리고 (비교적) 논란 없이 Python 3.3 이상과 상위 호환되도록 만들 수 있습니다.
참고 문헌
Copyright
This document has been placed in the public domain.