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”를 붙여 이진수 리터럴을 지원합니다.
- 문자열 형식 지정에서 이진수에 대한 지원을 제공합니다.
동기
이 PEP는 서로 다른 두 가지 문제에서 비롯되었습니다.
- 정수의 기본 8진수 표현은 C와 유사한 언어에 익숙하지 않은 사람들에게 말없이 혼란을 줍니다. Python 언어 자체에서 ‘013’은 ‘10진수 13’이 아니라 ‘10진수 11’을 의미하므로, 대부분의 사람이 이 리터럴에 부여할 의미와 다르며 잘못된 값의 정수 객체를 실수로 만드는 일이 매우 쉽습니다.
- 일부 Python 사용자는 언어에서 이진수를 강력하게 지원하기를 원합니다.
사양
문법 사양
문법이 변경됩니다. 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를 업데이트해야 합니다.
int() 사양
int(s, 0)도 새 문법 정의와 일치하게 됩니다.
이는 문법 변경에 필요한 PyLong_FromString 변경으로 자동으로 이루어져야 합니다.
또한 int() 문서를 변경하여 int(s)가 int(s, 10)과 동일하게 작동한다는 점을 설명하고, 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 어느 쪽에서도 % 연산자는 이진수 출력을 지원하지 않습니다. 이는 선호되는 문자열 형식 지정 방법인 PEP 3101(str.format)이 이미 이진수 출력을 지원하기 때문입니다.
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진수를 지정하는 역사적인 방식은 혼란을 일으키며 이진수 리터럴에 대한 추가 지원도 있으면 좋겠다는 점이 드러났습니다.
이 문서 전체에서 달리 명시하지 않는 한, 정수의 문자열 표현에 관한 논의는 다음 기능과 관련됩니다.
- 일반적인 모듈 컴파일, 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 이하의 명시적 진법을 받아들이며, 후자의 숫자는 ASCII 알파벳에 있는 숫자와 같은 대소문자의 문자 개수 합과 의심스러울 만큼 산술적으로 유사합니다.
그러나 포함을 주장하는 최선의 근거에는 이를 뒷받침할 사용 사례가 있어야 하므로, 모든 진법을 지원한다는 생각은 빠르게 거부되었고, 실질적인 지지를 받은 진법으로 남은 것은 십진법, 16진법, 8진법, 2진법뿐이었습니다.
특정 진법이 메일링 리스트에서 목소리 큰 지지자를 가지고 있다고 해서 반드시 그 진법이 실제로 언어에 포함되어야 한다는 뜻은 아니므로, 이 절의 나머지 부분에서는 이러한 특정 진법이 다른 가능한 선택지와 비교해 얼마나 유용한지를 논합니다.
인간은 다른 수의 밑을 끊임없이 사용합니다. 제가 지금이 오후 12시 30분이라고 말하면, 위의 “합의된” 목록에는 그중 하나만 포함된, 엄밀히 말해 서로 다른 세 가지 진법(12, 60, 2)으로 구성된 정량적 정보를 전달한 셈입니다. 그러나 그 정보의 전달에는 12진법과 60진법 정보 각각에 십진수 두 자릿수가 사용되었고, 역설적으로 십진수 한 자릿수에 담을 수 있었던 정보에 두 글자가 사용되었습니다.
따라서 일반적으로 인간은 “일반적인”(컴퓨터가 아닌) 수치 정보를 이름(AM, PM, January, …)이나 십진 표기법을 통해 전달합니다. 당연히 이름은 많은 항목으로 이루어진 집합에는 거의 사용되지 않으므로, 그 밖의 모든 것에는 십진법이 사용됩니다. 왜 그런지 설명하려는 연구들이 있으며, 일반적으로 아라비아 숫자 체계가 인간의 인지에 적합하다는 예상된 결론에 도달합니다. [3]
컴퓨터 설계의 역사에도 십진 표기법이 컴퓨터가 인간과 소통하는 올바른 방식임을 보여 주는 근거가 있습니다. 최초의 현대적 컴퓨터 중 하나인 ENIAC [4]는 이미 2진법으로 작동하는 컴퓨터들이 존재했음에도 십진법으로 계산했습니다.
십진 컴퓨터 연산은 매우 중요했기 때문에, 널리 사용되는 PC를 포함한 많은 컴퓨터에는 “binary coded decimal”(BCD) [5] 에서 작동하도록 설계된 명령어가 있으며, 이는 각 십진수 자릿수에 4비트를 할당하는 표현입니다. 이 명령어들은 많은 수에 대해 수행된 가장 복잡한 계산이 실제로 그 수에 대한 텍스트 I/O를 수행하는 데 필요한 계산이었던 시대부터 존재합니다. 표시되는 각 자릿수마다 나눗셈/나머지 연산을 수행하지 않고도 BCD를 표시할 수 있으며, 대부분의 하드웨어에 빠른 나눗셈 기능이 없던 당시에는 이것이 계산 측면에서 엄청난 이점이었습니다. BCD 사용에 기여한 또 다른 요인은 BCD 계산에서는 반올림이 인간이 수행하는 것과 정확히 같은 방식으로 이루어진다는 점이므로, 이진법이 계산 및 저장 측면에서 우수함에도 불구하고 BCD는 금융과 같은 분야에서 여전히 때때로 사용됩니다.
따라서 컴퓨터 자체가 일반적으로 효율적인 계산과 데이터 저장을 위해 2진법을 사용한다는 사실이 없었다면, 정수의 문자열 표현은 아마도 항상 십진법이었을 것입니다.
안타깝게도 컴퓨터 하드웨어는 인간처럼 생각하지 않으므로 프로그래머와 하드웨어 엔지니어는 종종 컴퓨터처럼 생각해야 하며, 이는 Python이 인간이 이해할 수 있는 형태로 이진 데이터를 전달하는 기능을 갖추는 것이 중요하다는 뜻입니다.
이진 데이터 표기법이 인간이 인지적으로 쉽게 처리할 수 있어야 한다는 요구 사항은 기호당 이진 자릿수(비트)의 개수가 정수여야 하며, 그 밖의 측면에서는 검증된 표준 십진 표기법(위치가 거듭제곱을 나타냄, 더 큰 크기는 왼쪽에 표시됨, 알파벳에 기호가 너무 많지 않음 등)을 상당히 충실하게 따라야 한다는 뜻입니다.
따라서 이 이진 데이터 표기법의 명백한 ‘최적 지점’은 8진수이며, 아라비아 숫자 알파벳에서 선택한 하나의 기호에 가능한 가장 큰 정수 개수의 비트를 담습니다.
실제로 PDP8 및 8080/Z80과 같은 일부 컴퓨터 아키텍처는 8진수를 기준으로 정의되었으며, 명령어의 비트 필드를 3개씩 그룹으로 배열하고 8진수 표현을 사용하여 명령어 집합을 설명했습니다.
오늘날에도 Unix 파일 권한 마스크와 같이 필드당 3비트로 구성된 비트 패킹 구조 때문에 8진수는 중요합니다.
그러나 더 큰 수에 사용할 때 8진수에는 단점이 있습니다. 기호당 비트 수는 정수이지만 그 자체가 2의 거듭제곱은 아닙니다. 이러한 제약으로 인해 16진수가 사용되게 되었으며, 오늘날 대부분의 컴퓨터에서 워드 크기가 2의 거듭제곱이라는 점을 고려하면, 각 기호가 4비트를 포함하기 때문에 10진수보다 60% 더 큰 알파벳이 필요함에도 16진수는 8진수보다 더 널리 사용됩니다.
Unix 파일 권한 마스크와 같은 일부 수는 8진수로 표현하면 사람이 쉽게 디코딩할 수 있지만 16진수로는 디코딩하기 어렵고, 반면 다른 수는 사람이 16진수로 처리하기가 훨씬 쉽습니다.
안타깝게도 컴퓨터에서 사용되는 이진수 중에는 16진수나 8진수 어느 쪽으로도 잘 전달되지 않는 것들이 있습니다. 다행히도 정기적으로 이러한 수를 다루어야 하는 사람은 더 적지만, 다른 한편으로 이는 토론 목록의 여러 사람이 Python에 직접적인 이진수 표현을 추가하는 것이 현명한지에 의문을 제기했다는 것을 의미합니다.
이러한 수가 매우 유용한 한 가지 분야는 하드웨어 레지스터를 읽고 쓰는 작업입니다. 때때로 하드웨어 설계자는 사람이 읽기 쉬운 표현을 포기하고 주소 공간 효율성을 택하여, 여러 비트 필드를 비정렬 비트 위치에서 하나의 하드웨어 레지스터에 패킹합니다. 그러면 한 16진수 숫자의 상위 3비트와 다음 16진수 숫자의 하위 2비트로 구성된 5비트 필드를 사람이 재구성하는 일은 번거롭고 오류가 발생하기 쉽습니다.
Python이 이진 정보를 사람에게 전달하는 능력이 인구의 작은 기술적 하위 집단에만 유용하더라도, 바로 그 하위 집단에 Python 핵심 팀 구성원의 대부분 또는 전부가 포함되어 있으므로, 이러한 표기법 중 가장 유용성이 낮은 직접적인 이진수조차 Python 커뮤니티에서 여러 열렬한 지지자와 거의 또는 전혀 없는 확고한 반대자를 보유합니다.
지원되는 진법의 구문
이 제안에서는 8진수에 대문자 또는 소문자 “o”를 사용하는 “0o” 접두사와, 이진수에 대문자 또는 소문자 “b”를 사용하는 “0b” 접두사를 사용합니다.
대문자를 지원하지 말자는 강력한 지지가 있었지만, 복소수의 ‘j’, 지수의 ‘e’, 원시 문자열의 ‘r’(몇 가지 예를 들자면)이 이미 대문자를 지원하고 있으므로 이는 다른 PEP에서 다룰 별도의 주제입니다.
서로 다른 진법을 구분하는 구문은 Python-3000 논의에서 많은 주목을 받았습니다. 이 구문에는 몇 가지 요구 사항과 ‘있으면 좋은 기능’이 있습니다(때로는 서로 충돌합니다).
- 입력 구문과 출력 구문(예: 문자열 % 연산자 구문) 모두에서 합리적인 범위 내에서 다른 언어 및 이전 버전의 Python과 최대한 호환되어야 합니다.
- 일반적인 관찰자가 최대한 쉽게 알아볼 수 있어야 합니다.
- 서로 다른 진법으로 형식화된 정수를 시각적으로 쉽게 구별할 수 있어야 합니다.
제안된 구문에는 16진수의 256을 나타내는 16r100과 같은 임의 진법 접두사와, 100h 어셈블러 스타일 접미사와 유사한 진법 접미사 등이 포함되었습니다. 문자 “O”를 8진수에 사용할 수 있는지에 대한 논쟁은 치열했습니다. 일부 글꼴에서 대문자 “O”가 0과 매우 비슷하게 보이기 때문입니다. “oCtal”의 두 번째 글자인 “c”를 사용하거나, “heXadecimal”의 “x”에 맞춰 “ocTal”의 “t”와 “biNary”의 “n”을 사용하자는 제안도 있었습니다.
문자열 % 연산자에서는 이미 8진수를 나타내는 데 “o”가 사용되고 있었습니다. 이진 형식화는 % 연산자에 추가되지 않습니다. PEP 3101(고급 문자열 형식화)이 이미 이진수를 지원하며, % 형식화는 향후 폐기될 예정이기 때문입니다.
결국 대문자 “O”가 0처럼 보일 수 있고 대문자 “B”가 8처럼 보일 수 있으므로, 이러한 접두사는 소문자만 사용하기로 결정되었습니다. 그러나 원시 문자열의 ‘r’과 마찬가지로 이는 선호나 스타일 가이드의 문제일 수 있습니다.
미해결 문제
논의에서는 16진수의 ‘x’, 원시 문자열의 ‘r’, 지수화의 ‘e’, 복소수의 ‘j’와 같은 모든 숫자 및 문자열 특수 수정자에 소문자를 사용해야 한다는 의견이 제시되었습니다. 이는 별도의 PEP에서 다룰 문제입니다.
이 PEP는 입력에서 대문자와 소문자 중 어느 쪽을 사용할지에 대해 입장을 취하지 않으며, 일관성을 위해 다른 문자에 대한 입력 구문 분석에서 대문자를 제거하지 않을 것이라면 8진수와 이진수에도 대문자를 추가해야 한다는 점만 언급하고 이러한 가정에 따라 변경 사항을 문서화합니다. 아직 대소문자 문제에 관한 PEP가 없기 때문입니다.
출력 형식화는 다른 문제일 수 있습니다. 출력 형식 문자열에서 대소문자 구분을 적용한 선례가 이미 충분히 있으며, 문자열 % 연산자의 ‘대체 형식’이 이진수 또는 8진수 출력에 대문자 ‘B’ 또는 ‘O’ 문자를 지원해야 할 타당한 사용 사례가 있는지에 대한 합의가 필요합니다. 현재 PEP 3101은 이러한 대체 기능조차 지원하지 않으며, hex() 함수도 프로그래머가 ‘x’ 문자의 대소문자를 지정하도록 허용하지 않습니다.
‘0123’을 Python 3.0에서 리터럴 십진수로 허용해야 한다는 강한 의견이 여전히 있습니다. 이것이 올바른 조치라면, 추가 PEP에서 이를 쉽게 다룰 수 있습니다. 이 제안은 근거에서 설명한 이유로 ‘0123’이 유효한 8진수가 되지 않도록 하는 첫 단계만 밟습니다.
2.6 호환 변경 사항만 적용하는 2to3 변환기 옵션이 있습니까(또는 있어야 합니까)? 2.6 릴리스 전에 2.6 라이브러리 코드에 이를 실행해야 합니까?
hex() 및 oct()과 일치하는 bin() 함수를 추가해야 합니까?
고급 문자열 형식 지정 기능이 제공되면 hex()가 정말 그렇게 유용합니까?
참고 문헌
Copyright
This document has been placed in the public domain.