[Final] PEP 414 - Explicit Unicode Literal for Python 3.3

원문 링크: PEP 414 - Explicit Unicode Literal for Python 3.3

상태: Final 유형: Standards Track 작성일: 15-Feb-2012

PEP 414 – Python 3.3을 위한 명시적 유니코드 리터럴

  • 작성자: Armin Ronacher, Alyssa Coghlan
  • 상태: Final (최종)
  • 유형: Standards Track
  • 생성일: 2012년 2월 15일
  • Python 버전: 3.3
  • 결정: Python-Dev 메시지

개요 (Abstract)

이 문서는 유니코드 인식 Python 2 애플리케이션을 Python 3으로 포팅할 때 필요한 변경 사항의 양을 줄이기 위해 Python 2.x의 명시적 unicode 리터럴을 Python 3.x 언어 사양에 다시 통합할 것을 제안합니다.

BDFL (Benevolent Dictator For Life)의 선언 (BDFL Pronouncement)

이 PEP는 Python 3.3에 대해 공식적으로 승인되었습니다. Guido van Rossum은 “나는 이 PEP를 수락한다. 이것은 해롭지 않다. 실행하라.”고 언급했습니다.

제안 (Proposal)

이 PEP는 Python 3.3이 Python 2의 Unicode 리터럴 구문 지원을 복원할 것을 제안합니다. 이는 유니코드 인식 애플리케이션의 기존 Python 2 코드 중 상당 부분을 Python 3에서 수정 없이 실행할 수 있도록 합니다.

구체적으로, Python 3의 문자열 리터럴 프리픽스(prefix) 정의는 현재 지원되는 "r" | "R" 외에 "u" | "U"를 허용하도록 확장됩니다.

다음은 모두 일반적인 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” 유니코드 리터럴의 제외 (Exclusion of “Raw” Unicode Literals)

Python 2는 전통적인 raw string의 정의와는 다른 “raw” 유니코드 리터럴 개념을 지원했습니다. 예를 들어, \uXXXX\UXXXXXXXX 이스케이프 시퀀스는 여전히 컴파일러에 의해 처리되고 해당 유니코드 코드 포인트로 변환되었습니다.

Python 3에는 이에 해당하는 개념이 없습니다. 컴파일러는 raw string 리터럴의 내용을 전처리하지 않습니다. 이러한 문자열은 거의 사용되지 않으며, Python 3에서 허용될 경우 다르게 해석될 수 있으므로 완전히 제외하는 것이 더 나은 선택으로 결정되었습니다. 따라서 이러한 코드는 Python 3에서 즉시 Syntax Error를 발생시켜 잠재적으로 다른 출력을 생성하는 것을 방지합니다.

Python 2와 Python 3 모두에서 실행되는 동등한 동작을 얻으려면, 일반 유니코드 리터럴(문자열 내에서 적절한 추가 이스케이프 처리)을 사용하거나, 문자열 연결 또는 문자열 포맷팅을 통해 raw 부분과 유니코드 이스케이프 시퀀스 사용이 필요한 부분을 결합할 수 있습니다.

from __future__ import unicode_literals를 Python 2에서 사용할 때, 명목상 “raw” 유니코드 문자열 리터럴은 Python 2에서 “raw Unicode” 프리픽스로 명시적으로 표시된 문자열과 마찬가지로 \uXXXX\UXXXXXXXX 이스케이프 시퀀스를 처리합니다.

저자의 노트 (Author’s Note)

이 PEP는 원래 Armin Ronacher가 작성했으며, Guido의 승인은 해당 버전을 기반으로 이루어졌습니다. 현재 게시된 버전은 Alyssa Coghlan이 다시 작성하여 Guido가 결정할 때 고려했던 추가 역사적 세부 사항과 근거를 포함했습니다.

독자들은 이 PEP의 많은 주장이 기술적인 것이 아니라는 점을 인지해야 합니다. 대신, 소프트웨어 개발의 사회적, 개인적 측면과 밀접하게 관련되어 있습니다.

근거 (Rationale)

Python 3.2용 Web Services Gateway Interface (WSGI) 사양 (PEP 3333)의 Python 3 호환 버전이 출시되면서, Python 웹 생태계의 많은 부분은 기존 개발자 및 사용자 커뮤니티에 부정적인 영향을 주지 않으면서 Python 3을 지원하기 위한 공동의 노력을 기울여왔습니다.

이러한 커뮤니티의 주요 개발자들로부터 받은 주요 피드백 중 하나는 애플리케이션의 모든 유니코드 리터럴의 표기를 변경해야 하는 요구 사항이 포팅 노력에 있어 핵심적인 걸림돌이라는 것입니다. 특히, 다른 Python 3 변경 사항과 달리, 프레임워크라이브러리 작성자가 사용자 대신 쉽게 처리할 수 있는 문제가 아니었습니다. 대부분의 사용자는 Python 언어 사양의 “순수성”에 대해 크게 신경 쓰지 않으며, 단순히 자신의 웹사이트와 애플리케이션이 가능한 한 잘 작동하기를 원했습니다.

Python 웹 커뮤니티가 이 우려를 가장 크게 표명했지만, GUI 개발과 같이 유니코드 인식이 높은 다른 도메인도 Python 3을 지원하기 위한 노력을 시작하면서 유사한 문제에 직면할 것으로 예상됩니다.

일반적인 반대 의견 (Common Objections)

불만: 이 PEP는 Python 3.2 채택에 해를 끼칠 수 있습니다 (Complaint: This PEP may harm adoption of Python 3.2)

이 불만은 이 PEP가 유니코드 인식 Python 2 애플리케이션을 Python 3으로 포팅하는 것을 더 쉽게 만들 것이라는 암묵적인 인정을 담고 있다는 점에서 흥미롭습니다.

기존 포팅 도구 세트가 부과하는 제약 조건을 감수하거나, 문제를 최소화하기 위해 Python 2 코드베이스를 충분히 업데이트할 준비가 된 많은 Python 커뮤니티가 있습니다. 이 PEP는 그러한 커뮤니티를 위한 것이 아닙니다. 대신, 이러한 어려움을 겪고 싶지 않은 사람들을 돕기 위해 특별히 고안되었습니다.

그러나 이 제안은 의미론적 변경 없이 언어 구문에 대한 비교적 작은 조정이므로, 서드 파티 import hook으로 지원하는 것이 가능합니다. 이러한 import hookimport 시간 오버헤드를 부과하고, hook을 제자리에 배치하기 위해 이를 필요로 하는 각 애플리케이션에서 추가 단계를 요구하지만, Python 3.2를 대상으로 하는 애플리케이션이 유니코드 리터럴 프리픽스 사용으로 인해 Python 3.3+에서만 실행될 라이브러리프레임워크를 사용할 수 있도록 합니다.

Vinay Sajip의 uprefix가 이러한 import hook 프로젝트의 한 예입니다. 코드를 import 시점에 즉시 변환하는 것보다 미리 변환하는 것을 선호하는 사람들을 위해, Armin Ronacher는 import 시점이 아닌 install 시점에 실행되는 hook을 개발 중입니다. 두 가지 접근 방식을 결합하는 것도 가능합니다. 예를 들어, import hook은 로컬 개발 중 빠른 편집-테스트 주기(edit-test cycles)에 사용될 수 있지만, install hookContinuous Integration (지속적 통합) 작업 및 Python 3.2 배포에 사용될 수 있습니다.

불만: Python 3은 Python 2로부터의 포팅을 지원하기 위해 더 나빠져서는 안 됩니다 (Complaint: Python 3 shouldn’t be made worse just to support porting from Python 2)

이것은 실제로 Python 3의 핵심 설계 원칙 중 하나입니다. 그러나 Python 전체의 핵심 설계 원칙 중 하나는 “실용성이 순수성을 이긴다(practicality beats purity)”는 것입니다. 서드 파티 개발자에게 상당한 부담을 가할 것이라면, 그렇게 해야 할 확실한 근거가 있어야 합니다.

대부분의 경우, 하위 호환되지 않는 Python 3 변경 사항의 근거는 코드 정확성 향상(예: 바이너리 및 텍스트 데이터의 엄격한 기본 분리, 필요한 경우 integer divisionfloat로 업그레이드), 일반적인 메모리 사용량 감소(예: 구체적인 list보다 iteratorview 사용 증가), 또는 표현력을 높이지 않으면서 Python 코드를 읽기 어렵게 만드는 산만한 불편 사항 제거(예: 잡힌 예외에 이름을 지정하는 쉼표 기반 구문)였습니다. 이러한 추론에 의해 뒷받침되는 변경 사항은 Python 2 개발자들이 Python 3으로 전환하려는 시도에서 어떤 반대가 있더라도 되돌려지지 않을 것입니다.

Python 2에서는 역사적인 이유로 두 가지 방법이 제공되는 경우가 많았습니다. 예를 들어, 불평등은 !=<> 모두로 테스트할 수 있었고, 정수 리터럴은 선택적 L 접미사로 지정할 수 있었습니다. 이러한 중복은 Python 3에서 제거되어 언어의 전체 크기를 줄이고 개발자 간의 일관성을 향상시켰습니다.

원래 Python 3 설계(Python 3.2까지 포함)에서는 유니코드 리터럴에 대한 명시적 prefix 구문이 Python 3에서 완전히 불필요하다는 이유로 이 범주에 속하는 것으로 간주되었습니다. 그러나 이러한 다른 경우와 유니코드 리터럴의 차이점은 유니코드 리터럴 prefix가 Python 2 코드에서 중복되지 않는다는 것입니다. 이는 정보 손실을 피하기 위해 어떤 식으로든 보존되어야 하는 프로그래밍적으로 중요한 구별입니다.

porting tool이 전환을 돕기 위해 만들어졌지만, 여전히 Python 2에서 유니코드 문자열을 많이 사용하는 사용자에게 추가적인 부담을 줍니다. 이는 단지 미래의 개발자들이 Python 3을 배울 때 “역사적인 이유로 문자열 리터럴에 선택적 u 또는 U 프리픽스가 있을 수 있습니다. 직접 사용하지 마십시오. 이전 버전의 언어에서 포팅을 돕기 위해 있을 뿐입니다.”라는 말을 들을 필요가 없도록 하기 위함입니다.

Python 2를 배우는 많은 학생들이 string exception에 관해 비슷한 경고를 받았지만, Python 개발자로서의 성장에 혼란을 겪거나 회복할 수 없는 지장을 받지 않았습니다. 이 기능도 마찬가지일 것입니다.

Python 3이 bytes 리터럴과 raw bytesstring 리터럴에 대해 대문자 BR 프리픽스를 여전히 허용한다는 사실은 이 주장을 더욱 강화합니다. 문자열 프리픽스 변형으로 인한 혼란 가능성이 그렇게 중요하다면, Python 3에서 제거된 다른 모든 중복과 함께 이러한 중복 프리픽스를 제거하라는 요구는 어디에 있었습니까?.

string exception에 대한 지원이 일반적인 deprecation 프로세스를 사용하여 Python 2에서 제거된 것처럼, 중복 string prefix 문자(특히 B, R, u, U)에 대한 지원은 이 PEP의 현재 수용 여부와 관계없이 결국 Python 3에서 제거될 수 있습니다. 그러나 이러한 변경은 Python 2.7을 지원하는 서드 파티 라이브러리가 오늘날 Python 2.2 또는 2.3을 지원하는 라이브러리만큼 흔해진 후에야 발생할 가능성이 높습니다.

불만: WSGI “native strings” 개념은 보기 흉한 해킹입니다 (Complaint: The WSGI “native strings” concept is an ugly hack)

유니코드 리터럴의 제거가 웹 개발 커뮤니티에서 그러한 우려를 불러일으킨 한 가지 이유는 업데이트된 WSGI 사양이 WSGI 호환 인터페이스를 제공하는 기존 웹 서버에 대한 중단을 최소화하기 위해 몇 가지 타협을 해야 했기 때문입니다.

그러한 타협 중 하나가 “native string” 개념입니다. WSGI는 세 가지 다른 종류의 문자열을 정의합니다:

  • text strings: Python 2에서는 unicode로, Python 3에서는 str로 처리됩니다.
  • native strings: Python 2와 Python 3 모두에서 str로 처리됩니다.
  • binary data: Python 2에서는 str로, Python 3에서는 bytes로 처리됩니다.

일부 개발자들은 WSGI의 “native strings“를 보기 흉한 해킹으로 간주합니다. 이는 기본 데이터의 실제 인코딩과 관계없이 latin-1로 디코딩된 “텍스트”에만 사용되는 것으로 명시적으로 문서화되어 있기 때문입니다. 이 접근 방식은 텍스트 인코딩의 올바른 처리를 장려하도록 설계된 Python 3의 데이터 모델에 대한 많은 업데이트를 우회합니다. 그러나 HTTP 및 관련 프로토콜을 다룰 때 binary data와 텍스트 사이의 경계가 얼마나 모호해질 수 있는지, 그리고 인코딩된 텍스트 데이터를 조작할 때 사용되는 인코딩의 의미를 이해하는 것이 얼마나 중요한지 가장 잘 아는 사람들이 웹 서버 및 웹 프레임워크 개발자들이라는 문제 영역의 특정 세부 사항 때문에 일반적으로 작동합니다. 애플리케이션 수준에서는 이러한 세부 사항의 대부분이 웹 프레임워크 및 지원 라이브러리에 의해 개발자로부터 숨겨집니다.

실제로 “native strings“는 유용한 개념입니다. str과 주로 작동하도록 설계된 일부 API(표준 라이브러리 및 서드 파티 프레임워크패키지 모두)와 일부 내부 인터프리터 세부 사항이 있기 때문입니다. 이러한 구성 요소는 종종 Python 2에서 unicode 또는 Python 3에서 bytes를 지원하지 않거나, 지원하더라도 추가 인코딩 세부 사항을 요구하거나 str 변형에는 적용되지 않는 제약을 부과합니다.

실제 str 인스턴스를 사용하여 가장 잘 처리되는 인터페이스의 예시는 다음과 같습니다:

  • Python 식별자 (attribute, dict key, class 이름, module 이름, import reference 등)
  • 대부분의 URL 및 urllib/http 서버의 HTTP header
  • WSGI 환경 키 및 CGI 상속 값
  • 동적 컴파일 및 AST hack을 위한 Python 소스 코드
  • 예외 메시지
  • __repr__ 반환 값
  • 선호하는 파일 시스템 경로
  • 선호하는 OS 환경

Python 2.6 및 2.7에서는 이러한 구별이 다음과 같이 가장 자연스럽게 표현됩니다:

  • u"" : text string (unicode)
  • "" : native string (str)
  • b"" : binary data (str, bytes로도 별칭 지정)

Python 3에서는 latin-1로 디코딩된 native string이 다른 text string과 구별되지 않습니다:

  • "" : text string (str)
  • "" : native string (str)
  • b"" : binary data (bytes)

from __future__ import unicode_literals를 사용하여 Python 2의 동작을 수정하는 경우, n()의 적절한 정의와 함께 구별은 다음과 같이 표현될 수 있습니다:

  • "" : text string
  • n("") : native string
  • b"" : binary data

Python 2와 Python 3의 공통 부분집합(소스 인코딩 및 u()b() 헬퍼 함수의 적절한 사양 포함)에서는 다음과 같이 표현할 수 있습니다:

  • u("") : text string
  • "" : native string
  • b("") : binary data

이 마지막 접근 방식은 Python 2.5 이하를 지원하는 유일한 변형입니다. 모든 대안 중에서 Python 2.6 및 2.7에서 현재 지원되는 형식은 세 가지 원하는 동작을 명확하게 구별하는 가장 깔끔한 접근 방식입니다. 이 PEP를 통해 이 형식은 Python 3.3+에서도 지원될 것입니다. 또한 importinstall hook을 통해 Python 3.1 및 3.2에서도 지원될 것입니다. hook이 Python 2.5에서 b 프리픽스를 사용할 수 있도록 적용될 수 있다는 것도 가능성이 훨씬 낮지만 상상할 수 있습니다.

불만: 기존 도구는 모든 사람에게 충분해야 합니다 (Complaint: The existing tools should be good enough for everyone)

이미 애플리케이션을 Python 3으로 성공적으로 포팅한 개발자들 사이에서 일반적으로 표현되는 감정은 “어렵다고 생각한다면, 잘못하고 있는 것입니다” 또는 “그리 어렵지 않습니다, 그냥 시도해 보세요!”와 같은 것입니다. 의도치 않은 것이겠지만, 이러한 답변들은 현재 포팅 도구 세트의 부적절함을 지적하는 사람들에게 “포팅 도구에는 문제가 없습니다. 당신이 형편없고 제대로 사용할 줄 모르는 것입니다”라고 말하는 효과를 줍니다.

이러한 답변은 사람들이 불평하는 요점을 완전히 놓치고 있는 경우입니다. 이 PEP를 초래한 피드백은 포팅이 불가능하다고 사람들이 불평하기 때문이 아닙니다. 대신, 피드백은 포팅을 성공적으로 완료했지만, 포팅해야 했던 애플리케이션 클래스(특히 유니코드 인식 웹 프레임워크 및 지원 라이브러리)에 대해 그 경험이 매우 불쾌했다는 것을 반대하는 사람들로부터 온 것입니다.

이는 주관적인 평가이며, Python 3 포팅 도구 생태계가 “하나의 명백한 방법으로 그것을 하는” 철학이 단호하게 적용되지 않는 경우인 이유입니다. 원래는 “Python 2에서 개발하고, 2to3으로 변환하고, 둘 다 테스트하는” 것이 두 버전 모두에 대해 병렬로 개발하는 표준적인 방법이 될 것으로 의도되었지만, 실제로는 다양한 프로젝트와 개발자 커뮤니티의 요구 사항이 충분히 다양하여 각 그룹이 자신의 요구에 가장 적합한 접근 방식을 선택할 수 있도록 다양한 접근 방식이 고안되었습니다.

Lennart Regebro는 사용 가능한 마이그레이션 전략에 대한 훌륭한 개요를 제공했으며, 유사한 검토가 공식 포팅 가이드에 제공됩니다. (Lennart가 개요를 작성한 이후로 공식 지침은 “특정 상황에 따라 다릅니다”로 완화되었습니다.)

그러나 이러한 가이드 모두는 관련된 모든 개발자가 이미 Python 3을 지원하겠다는 생각에 전념하고 있다는 기본 가정 하에 작성되었습니다. 명확한 이점 없이 혼란을 특히 관대하게 여기지 않을 수 있는 사용자층과 상호 작용할 때, 또는 Python 2에 중점을 둔 업스트림 개발자들에게 Python 3의 향후 호환성을 개선하기 위한 패치만을 수락하도록 설득하려고 할 때 이러한 변경 사항의 사회적 측면에 대한 고려는 하지 않습니다.

현재의 포팅 도구 세트로는 모든 마이그레이션 전략이 프로젝트의 모든 유니코드 리터럴에 변경을 가져올 것입니다. 예외는 없습니다. unicode_literals import를 채택하기로 결정했다면 prefix 없는 문자열 리터럴로 변환되거나 u("text")와 같은 converter call로 변환될 것입니다.

unicode_literals import 접근 방식이 사용되지만 전체 프로젝트에서 동시에 채택되지 않으면, 일반 문자열 리터럴의 의미가 성가시게 모호해질 수 있습니다. 이 문제는 Django 사이트와 같이 통합된 소프트웨어에 특히 해로울 수 있습니다. 이러한 상황에서는 일부 파일이 unicode_literals import를 사용하고 다른 파일은 사용하지 않아 혼란을 초래할 확실한 가능성이 있습니다.

이러한 문제는 기술적 수준에서 명확하게 해결할 수 있지만, 사회적 수준에서는 완전히 불필요한 산만함입니다. 개발자의 에너지는 Python 3 전환과 관련된 실제 기술적 어려움(예: 8비트 텍스트 문자열과 바이너리 데이터를 구별하는 것)을 해결하는 데 할애되어야 합니다. 그들은 Python 2에서 이미 유니코드 문자열을 명시적으로 식별했다는 이유만으로 추가적인 코드 변경(자동화된 변경이라 할지라도)으로 벌을 받아서는 안 됩니다.

Armin Ronacher는 cross-version compatibility six library의 지원을 받아 Python 2.7 이상에서 실행되는 정도로만 Python 코드를 현대화하는 2to3의 실험적 확장을 만들었습니다. 이 도구는 python-modernize로 제공됩니다. 현재 이 도구로 생성된 델타는 변환된 소스의 모든 유니코드 리터럴에 영향을 미칩니다. 이는 이러한 변경 사항을 수락하도록 요청받은 업스트림 개발자들 사이에서, 그리고 애플리케이션 변경을 요청받은 프레임워크 사용자들 사이에서 정당한 우려를 불러일으킬 것입니다.

그러나 유니코드 리터럴 구문 변경으로 인한 “노이즈”를 제거함으로써, 많은 프로젝트가 python-modernize를 실행하고 권장 변경 사항을 적용하는 것만으로 Python 3.3+와 호환되도록 깔끔하고(비교적) 논란 없이 forward compatible하게 만들 수 있었습니다.

참조 (References)

  • Python-Modernize
  • Porting to Python 3: Migration Strategies
  • Porting Python 2 Code to Python 3
  • uprefix import hook project
  • install hook to remove unicode string prefix characters

이 번역은 PEP 414의 핵심 내용을 한국어 Python 개발자 커뮤니티가 이해하기 쉽도록 전문성과 가독성을 유지하며 작성되었습니다.

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments