PEP 3127 – 정수 리터럴 지원 및 문법
- Author:
- Patrick Maupin <pmaupin at gmail.com>
- Discussions-To:
- Python-3000 list
- Status:
- Final
- Type:
- Standards Track
- Created:
- 14-Mar-2007
- Python-Version:
- 3.0
- Post-History:
- 18-Mar-2007
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 서로 다른 기수(진법)로 표현한 정수의 문자열 리터럴 처리 방식을 합리적으로 정리하기 위해 Python 코어를 변경할 것을 제안합니다. 이 변경 사항은 Python 3.0을 대상으로 하지만, 변경 사항 중 하위 호환성을 유지하는 부분은 Python 2.6에도 추가하여 3.0에서 유효한 모든 정수 리터럴이 2.6에서도 유효하도록 해야 합니다.
제안 내용은 다음과 같습니다.
- 이제 8진수 리터럴은 “0” 대신 “0o” 또는 “0O”를 앞에 붙여 지정해야 합니다.
- 이제 “0b” 또는 “0B”를 앞에 붙인 2진수 리터럴을 지원합니다.
- 문자열 포매팅에서 2진수를 다룰 수 있도록 합니다.
동기
이 PEP는 서로 다른 두 가지 문제에서 비롯되었습니다.
- 정수의 기본 8진수 표현은 C 계열 언어에 익숙하지 않은 사람들을 모르는 사이에 혼란스럽게 만듭니다. 잘못된 값을 가진 정수 객체를 실수로 만들기가 매우 쉽습니다. Python 언어 자체에서 ‘013’은 ‘10진수 13’이 아니라 ‘10진수 11’을 뜻하는데, 이는 대부분의 사람이 이 리터럴에 부여할 의미가 아니기 때문입니다.
- 일부 Python 사용자는 언어 차원의 2진수 지원을 강하게 원합니다.
명세
문법 명세
문법이 변경됩니다. Python 2.6에서 변경되거나 새로 추가되는 토큰 정의는 다음과 같습니다.:
integer ::= decimalinteger | octinteger | hexinteger |
bininteger | oldoctinteger
octinteger ::= "0" ("o" | "O") octdigit+
bininteger ::= "0" ("b" | "B") bindigit+
oldoctinteger ::= "0" octdigit+
bindigit ::= "0" | "1"
Python 3.0에서는 “oldoctinteger”를 지원하지 않으며, 리터럴이 “0”으로 시작하고 두 번째 문자가 숫자이면 예외가 발생합니다.
두 버전 모두 문법뿐 아니라 PyLong_FromString도 변경해야 합니다.
문서도 변경해야 합니다. grammar.txt와 레퍼런스 매뉴얼의 정수 리터럴 절이 여기에 해당합니다.
다른 문제가 없는지 PEP 306을 확인해야 하며, 그 PEP에 설명된 절차가 충분하지 않다면 해당 PEP를 갱신해야 합니다.
int() 명세
int(s, 0)도 새 문법 정의를 따릅니다.
이는 문법 변경에 필요한 PyLong_FromString 변경으로 자동으로 이루어질 것입니다.
또한 int(s)가 int(s, 10)과 똑같이 동작한다는 점을 설명하도록 int() 문서를 변경해야 하며, int(s, 0)의 설명에서 “guess”라는 단어를 제거해야 합니다.
long() 명세
Python 2.6에서는 새 문법을 반영하도록 long()의 구현과 문서를 변경해야 합니다.
토크나이저 예외 처리
잘못된 토큰이 “0”으로 시작하면, 예외 오류 메시지는 현재의 “SyntaxError: invalid token”보다 더 많은 정보를 제공해야 합니다. 10진수는 0으로 시작할 수 없으며, 8진수는 맨 앞의 0 뒤에 “o”가 필요하다는 점을 설명해야 합니다.
int() 예외 처리
문자열을 인자로 int()를 호출할 때 발생하는 ValueError는 적어도 오류 메시지에 기수를 명시적으로 포함해야 합니다. 예를 들면 다음과 같습니다.:
ValueError: invalid literal for base 8 int(): 09
oct() 함수
oct()는 8진수 숫자 앞에 ‘0o’를 출력하도록 갱신되어야 합니다(3.0 및 2.6 호환 모드).
출력 포매팅
3.0에서는 문자열 % 연산자의 ‘o’ 옵션에 대한 대체 문법이 ‘0’ 대신 ‘0o’를 앞에 붙이도록 갱신되어야 합니다. 2.6에서는 대체 8진수 포매팅이 계속해서 ‘0’만 앞에 붙입니다. 2.6과 3.0 어느 쪽에서도 % 연산자는 2진수 출력을 지원하지 않습니다. 이는 선호되는 문자열 포매팅 방법인 PEP 3101 (str.format)이 이미 2진수 출력을 지원하기 때문입니다.
2.6에서 3.0으로의 전환
2to3 변환기는 모든 8진수 문자열 리터럴에 ‘o’를 삽입해야 합니다.
Python 2.6의 Py3K 호환 옵션은 oldoctinteger 리터럴을 사용하려는 시도가 예외를 일으키도록 해야 합니다.
근거
이 문제에 관한 논의의 대부분은 2007년 3월 14일부터 Python-3000 메일링 리스트에서 이루어졌으며, 숫자로 이루어진 문자열 앞에 “0”을 붙이면 그 숫자 문자열의 의미가 완전히 바뀐다는 사실을 알게 되면 보통 사람은 완전히 당혹스러워할 것이라는 지적이 그 계기였습니다.
이 논의 중에, 2006년 1월에도 같은 문제의 발견을 계기로 이 주제에 대해 비슷하지만 더 짧은 논의가 있었다는 점이 지적되었습니다.
배경
역사적인 이유로, 문자열 포매팅과 토큰 리터럴에 쓰이는 Python의 여러 진법(기수) 정수 문자열 표현은 C에서 많은 부분을 차용했습니다. [1] [2] 사용 경험을 통해, 8진수를 지정하는 기존 방식은 혼란스럽다는 점과 2진수 리터럴을 추가로 지원하면 좋겠다는 점이 드러났습니다.
이 문서 전체에서 따로 언급하지 않는 한, 정수의 문자열 표현에 관한 논의는 다음 기능들과 관련됩니다:
- 일반적인 모듈 컴파일, eval(), int(token, 0)에서 사용되는 리터럴 정수 토큰입니다. (int(token)과 int(token, 2-36)은 이 제안으로 변경되지 않습니다.)
- 2.6에서 long()은 int()와 동일하게 취급됩니다
- % 문자열 연산자 또는 새로운 PEP 3101 고급 문자열 포매팅 방법을 통해 정수를 문자열로 포매팅하는 기능입니다.
다음을 전제로 합니다:
- 일관성을 위해, 이 모든 기능은 동일한 지원 기수 집합을 가져야 합니다.
- Python 소스 코드 문법과 int(mystring, 0)은 계속해서 동일한 동작을 공유해야 합니다.
기존 8진수 문법의 제거
이 PEP는 Python 3.0(및 2.6의 Python 3.0 미리 보기 모드)에서 앞에 0을 붙여 8진수를 지정하는 기능을 언어에서 제거하고, 앞에 오는 “0” 바로 뒤에 다른 숫자가 오면 항상 SyntaxError를 일으킬 것을 제안합니다.
현재 논의에서 거의 만장일치로 동의한 것은:
eval('010') == 8
이 더 이상 참이어서는 안 된다는 점입니다. 새로운 사용자에게 혼란을 주기 때문입니다. 또한 다음과 같은 제안도 있었습니다.:
eval('0010') == 10
이 참이 되어야 한다는 것이지만, 이는 훨씬 더 논란의 여지가 있습니다. 다른 컴퓨터 언어의 용법과 너무 어긋나서 실수가 생기기 쉽기 때문입니다.
C/C++, Java, Perl, JavaScript를 포함하여 현재 널리 쓰이는 거의 모든 컴퓨터 언어는 앞에 0이 붙은 숫자열을 8진수로 취급합니다. 반대로 이러한 숫자를 10진수로 취급하자는 지지자들의 주장에도 매우 타당한 근거가 있습니다. 아래 Supported radices에서 논의하듯이, 컴퓨터 외의 세계 전체는 거의 전적으로 10진수만 사용합니다. 많은 사람이 10진수가 아닌 기수를 마주하면 당황하고 혼란스러워한다는 일화적 증거가 충분히 있습니다.
그러나 대부분의 상황에서 대부분의 사람은 십진수 앞에 불필요한 0을 쓰지 않습니다. 주된 예외는 숫자들을 열에 맞춰 정렬하려고 할 때입니다. 그러나 PEP 8은 Python 코드를 정렬하려고 공백을 사용하는 것을 명시적으로 권장하지 않으므로, 같은 목적으로 앞에 0을 붙이는 것에도 같은 인자가 적용되어야 한다고 짐작할 수 있습니다.
마지막으로, 이메일 논의는 누군가가 아직도 8진수를 실제로 사용하는지, 그리고 어쨌든 그런 옛 세대 사용자들의 요구를 들어주어야 하는지에 자주 집중되었지만, 이는 핵심에서 거의 완전히 벗어난 이야기입니다.
컴퓨팅을 완전히 처음 접하는 사람 중에서, 드물지만 가끔 또는 습관적으로 십진수에 앞자리 0을 실제로 사용하는 사람이 있다고 가정해 봅시다. Python은 다음 중 하나를 할 수 있습니다:
- 지금처럼 그들의 숫자를 아무 경고 없이 잘못 처리합니다;
- 이것이 유효한 문법이라는 생각을 즉시 바로잡아 줍니다(그리고 물론 SyntaxWarning이 지금보다 더 부드러워야 하겠지만, 그것은 다른 PEP에서 다룰 주제입니다); 또는
- 컴퓨터가 “0”으로 시작하는 여러 자리 십진 정수를 문제없이 받아들인다고 계속 생각하게 둡니다.
어떤 사람들은 (c)가 올바른 답이라고 열렬히 믿으며, 새로운 사용자가 결코 성장하여 AJAX 애플리케이션을 작성하기 시작하지 않으리라고 확신할 수 있다면 그들의 말이 전적으로 옳을 것입니다.
따라서 새로운 Python 사용자가 자신의 숫자가 제대로 동작하지 않는다는 사실을 뒤늦게 발견하고 (현재로서는) 당혹스러워할 수 있지만, 우리는 Python이 앞자리 0을 좋아하지 않는다는 것을 (바라건대 적절한 메시지와 함께!) 즉시 설명해 줌으로써 이를 해결할 수도 있고, 아니면 이 교육 경험을 브라우저의 JavaScript 인터프리터에 맡기고 그곳에서 문제를 디버깅하게 할 수도 있습니다.
지원되는 기수
이 PEP는 Python 언어에서 지원하는 기수를 2, 8, 10, 16으로 할 것을 제안합니다.
정수의 8진수(기수 8) 표현을 위한 옛 문법을 언어에서 제거해야 한다는 데 합의하고 나면, 다음으로 떠오르는 당연한 질문은 “8진수로 숫자를 지정(하고 표시)하는 방법이 실제로 필요한가?”입니다.
이 질문에 곧바로 “언어가 어떤 기수를 지원해야 하는가?”라는 질문이 뒤따릅니다. 컴퓨터는 시키는 일을 워낙 능숙하게 해내므로, 논의 중에 나온 솔깃한 답은 “전부”였습니다. 이 답은 분명 예전에도 나온 적이 있습니다 – int() 생성자는 2 이상 36 이하의 값을 갖는 명시적 기수를 받아들이며, 여기서 36이라는 숫자는 숫자 기호의 개수와 ASCII 알파벳에서 대소문자가 같은 글자의 개수를 합한 값과 수상할 정도로 산술적으로 일치합니다.
그러나 포함을 지지하는 가장 좋은 인자는 이를 뒷받침하는 사용 사례가 있어야 하므로, 모든 기수를 지원하자는 아이디어는 빠르게 거부되었고, 실질적인 지지를 받으며 남은 기수는 10진수, 16진수, 8진수, 2진수뿐이었습니다.
특정 기수를 메일링 리스트에서 목소리 높여 지지하는 사람이 있다고 해서 그 기수가 반드시 언어에 포함되어야 한다는 뜻은 아니므로, 이 절의 나머지 부분에서는 다른 가능한 선택지와 비교하여 이 특정 기수들의 유용성을 논합니다.
인간은 다른 진법을 끊임없이 사용합니다. 제가 지금이 오후 12시 30분이라고 말한다면, 저는 세 가지 서로 다른 진법(12, 60, 2)으로 구성되었다고 볼 수 있는 정량적 정보를 전달한 것이며, 그중 하나만이 위의 “합의된” 목록에 들어 있습니다. 하지만 그 정보의 전달에는 12진법 정보와 60진법 정보에 각각 두 자리 십진 숫자가 사용되었고, 기묘하게도 한 자리 십진 숫자로 충분히 나타낼 수 있었던 정보에 두 개의 글자가 사용되었습니다.
따라서 일반적으로 인간은 “일반적인”(컴퓨터와 무관한) 수치 정보를 이름(AM, PM, January, …)을 통하거나 십진 표기법을 사용하여 전달합니다. 당연히 이름은 항목이 많은 집합에는 거의 사용되지 않으므로, 그 밖의 모든 경우에는 십진법이 사용됩니다. 왜 그런지를 설명하려는 연구들이 있으며, 대개 아라비아 숫자 체계가 인간의 인지에 잘 맞는다는 예상된 결론에 도달합니다. [3]
컴퓨터 설계의 역사에서도 십진 표기법이 컴퓨터가 인간과 소통하는 올바른 방법임을 보여 주는 근거를 찾을 수 있습니다. 최초의 현대적 컴퓨터 중 하나인 ENIAC [4]은 이미 2진법으로 동작하는 컴퓨터가 존재했음에도 불구하고 십진법으로 계산했습니다.
십진 컴퓨터 연산은 매우 중요했기 때문에, 어디에나 있는 PC를 포함한 많은 컴퓨터에는 각 십진 자릿수에 4비트를 할당하는 표현인 “이진화 십진법”(BCD) [5]을 처리하도록 설계된 명령어가 있습니다. 이 명령어들은 많은 숫자에 대해 수행되는 가장 부담이 큰 계산이 사실상 그 숫자들로 텍스트 I/O를 수행하는 데 필요한 계산이던 시절부터 존재했습니다. BCD는 표시되는 모든 자릿수마다 나눗셈/나머지 연산을 하지 않고도 표시할 수 있으며, 대부분의 하드웨어에 빠른 나눗셈 기능이 없던 시절에는 이것이 계산상 큰 이점이었습니다. BCD 사용에 기여한 또 다른 요인은 BCD 계산에서는 반올림이 사람이 하는 것과 정확히 같은 방식으로 일어난다는 점이며, 그래서 연산과 저장 면에서 2진수가 우월한데도 BCD는 금융 같은 필드에서 여전히 가끔 사용됩니다.
따라서 컴퓨터 자체가 효율적인 계산과 데이터 저장을 위해 보통 2진법을 사용한다는 사실이 없었다면, 정수의 문자열 표현은 아마 항상 십진수였을 것입니다.
안타깝게도 컴퓨터 하드웨어는 인간처럼 생각하지 않으므로, 프로그래머와 하드웨어 엔지니어는 종종 컴퓨터처럼 생각할 수밖에 없으며, 이는 Python이 이진 데이터를 인간이 이해할 수 있는 형태로 전달하는 능력을 갖추는 것이 중요하다는 뜻입니다.
이진 데이터 표기법이 인간이 인지적으로 처리하기 쉬워야 한다는 요구 사항은, 그 표기법이 기호당 정수 개의 이진 자릿수(비트)를 담아야 하며, 그 밖의 면에서는 오랫동안 검증된 표준 십진 표기법(위치가 거듭제곱을 나타내고, 큰 자릿값이 왼쪽에 오며, 알파벳의 기호 수가 너무 많지 않은 것 등)을 상당히 충실하게 따라야 한다는 것을 의미합니다.
따라서 이 이진 데이터 표기법의 명백한 “최적점”은 8진수입니다. 8진수는 아라비아 숫자 알파벳에서 고른 기호 하나에 담을 수 있는 가장 많은 정수 개의 비트를 담습니다.
실제로 PDP8과 8080/Z80 같은 일부 컴퓨터 아키텍처는 8진수를 기준으로 정의되었습니다. 명령어의 비트 필드를 세 개씩 묶어 배열하고 명령어 집합을 8진 표현으로 기술했다는 의미에서 그렇습니다.
오늘날에도 Unix 파일 권한 마스크처럼 필드마다 3비트로 이루어진 비트 패킹 구조 때문에 8진수는 중요합니다.
하지만 8진수는 더 큰 수에 사용할 때 단점이 있습니다. 기호당 비트 수는 정수이기는 하지만 그 자체가 2의 거듭제곱은 아닙니다. 이 한계(요즘 대부분 컴퓨터의 워드 크기가 2의 거듭제곱이라는 점을 고려하면) 때문에 16진수가 생겨났습니다. 16진수는 10진수보다 60% 더 큰 알파벳이 필요한데도 기호마다 4비트를 담기 때문에 8진수보다 더 널리 쓰입니다.
Unix 파일 권한 마스크 같은 일부 수는 8진수로 나타내면 사람이 쉽게 해독할 수 있지만 16진수로는 해독하기 어렵고, 반대로 다른 수는 사람이 16진수로 다루는 편이 훨씬 쉽습니다.
안타깝게도 컴퓨터에서 쓰이는 이진수 중에는 16진수로도 8진수로도 잘 전달되지 않는 것들도 있습니다. 다행히 이런 수를 일상적으로 다뤄야 하는 사람은 비교적 적지만, 다른 한편으로 이 때문에 토론 메일링 리스트의 여러 사람이 Python에 순수 이진 표현을 추가하는 것이 현명한지 의문을 제기했습니다.
이런 수가 매우 유용한 예로 하드웨어 레지스터를 읽고 쓰는 경우를 들 수 있습니다. 하드웨어 설계자는 때때로 사람의 가독성을 포기하고 주소 공간 효율을 택하여, 여러 비트 필드를 정렬되지 않은 비트 위치에 두고 하나의 하드웨어 레지스터에 몰아넣습니다. 이때 한 16진 자릿수의 상위 3비트와 다음 16진 자릿수의 하위 2비트로 이루어진 5비트 필드를 사람이 재구성하는 일은 번거롭고 실수하기 쉽습니다.
Python이 사람에게 2진 정보를 전달하는 능력이 전체 인구 중 소수의 기술적 집단에게만 유용하다 하더라도, Python 코어 팀 구성원의 전부는 아니더라도 대부분이 바로 그 집단에 속하므로, 이 표기법들 중 가장 덜 유용한 순수 2진수조차도 Python 커뮤니티 안에서 열렬한 지지자가 여럿 있고 확고한 반대자는 거의 없습니다.
지원하는 기수의 문법
이 제안은 8진수에는 대문자 또는 소문자 “o”를 쓰는 “0o” 접두사를, 이진수에는 대문자 또는 소문자 “b”를 쓰는 “0b” 접두사를 사용하자는 것입니다.
대문자를 지원하지 말자는 강한 지지가 있었지만, 이는 다른 PEP에서 다룰 별개의 주제입니다. 몇 가지만 예로 들어도 복소수의 ‘j’, 지수의 ‘e’, 원시 문자열의 ‘r’은 이미 대문자를 지원하기 때문입니다.
서로 다른 기수를 구분하는 문법은 Python-3000 토론에서 많은 관심을 받았습니다. 이 문법에는 여러 가지 (때로는 서로 충돌하는) 요구 사항과 “있으면 좋은 것”이 있습니다:
- 입력 문법과 출력(예: 문자열 % 연산자) 문법 모두에서, 합리적인 범위 안에서 다른 언어 및 이전 버전의 Python과 최대한 호환되어야 합니다.
- 무심코 보는 사람에게도 최대한 명확해야 합니다.
- 서로 다른 진법으로 포매팅된 정수를 눈으로 쉽게 구별할 수 있어야 합니다.
제안된 문법에는 16r100(16진수로 256)과 같은 임의 기수 접두사, 그리고 어셈블러 스타일의 100h 접미사와 비슷한 기수 접미사 등이 있었습니다. 문자 “O”를 8진수에 쓸 수 있는지에 대한 논쟁은 치열했습니다. 일부 글꼴에서는 대문자 “O”가 0과 수상할 만큼 비슷해 보이기 때문입니다. “oCtal”의 두 번째 글자인 “c”를 쓰자는 제안도 있었고, 심지어 “heXadecimal”의 “x”에 맞추어 “ocTal”의 “t”와 “biNary”의 “n”을 쓰자는 제안도 있었습니다.
문자열 % 연산자에서는 이미 “o”가 8진수를 나타내는 데 쓰이고 있었습니다. PEP 3101(고급 문자열 포매팅)이 이미 이진수를 지원하고 % 포매팅은 앞으로 폐지될 예정이므로, % 연산자에는 이진수 포매팅을 추가하지 않습니다.
결국 대문자 “O”는 0처럼 보일 수 있고 대문자 “B”는 8처럼 보일 수 있으므로 이 접두사들은 소문자만 허용하기로 결정했습니다. 다만 원시 문자열의 ‘r’처럼 이는 선호나 스타일 가이드의 문제일 수 있습니다.
미해결 문제
토론에서는 16진수의 ‘x’, 원시 문자열의 ‘r’, 지수의 ‘e’, 복소수의 ‘j’처럼 모든 숫자 및 문자열 특수 수식자에 소문자를 사용해야 한다는 제안이 있었습니다. 이는 별도의 PEP에서 다룰 문제입니다.
이 PEP는 입력의 대소문자에 대해 어떤 입장도 취하지 않습니다. 다만 일관성을 위해, 다른 문자에 대해 입력 구문 분석에서 대문자를 제거하지 않는다면 8진수와 이진수에도 대문자를 추가해야 한다는 점을 언급하고, 대소문자 문제에 관한 PEP가 아직 없으므로 이 가정 아래에서 변경 사항을 문서화합니다.
출력 서식은 다른 문제일 수 있습니다. 출력 형식 문자열에서는 이미 대소문자를 구분하는 선례가 충분히 있으며, 2진수나 8진수 출력에 대문자 ‘B’나 ‘O’ 문자를 지원하도록 문자열 % 연산자의 “대체 형식”에 타당한 사용 사례가 있다는 합의가 필요할 것입니다. 현재 PEP 3101은 이 대체 기능조차 지원하지 않으며, hex() 함수는 프로그래머가 ‘x’ 문자의 대소문자를 지정할 수 없게 합니다.
Python 3.0에서 ‘0123’을 10진수 리터럴로 허용해야 한다는 강한 의견이 여전히 있습니다. 그렇게 하는 것이 옳다면, 이는 별도의 PEP에서 쉽게 다룰 수 있습니다. 이 제안은 근거에서 다룬 이유로 ‘0123’이 유효한 8진수가 되지 않도록 하는 첫 단계만 진행합니다.
2to3 변환기에 2.6과 호환되는 변경만 수행하는 옵션이 있습니까(또는 있어야 합니까)? 2.6 릴리스 전에 2.6 라이브러리 코드에 이를 실행해야 합니까?
hex() 및 oct()에 대응하는 bin() 함수를 추가해야 합니까?
고급 문자열 포매팅이 도입된 후에도 hex()가 정말 그렇게 유용합니까?
참고 문헌
Copyright
This document has been placed in the public domain.