PEP 786 – 정수 필드를 위한 정밀도 및 모듈로 정밀도 플래그 형식 지정자
- Author:
- Jay Berry <email at jb2170.com>
- Sponsor:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 04-Apr-2025
- Python-Version:
- 3.15
- Post-History:
- 14-Feb-2025, 09-Apr-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 정수 필드에 대해 PEP 3101의 표준 형식 지정자 .와 z를 각각 “정밀도”와 “모듈로 정밀도”로 구현할 것을 제안합니다. 두 지정자를 이 PEP에서 함께 다루는 이유는, 거부된 대안 구현들이 두 지정자의 상호 얽힌 조합을 수반하기 때문입니다.
. (“precision”)(“정밀도”)는 이전 스타일 % 형식 지정과 동일하게, 정수를 지정된 최소 자릿수로 형식화해야 합니다. 이는 'c'를 제외한 모든 정수 표시 형식에 구현되어야 합니다.
z (“modulo-precision”)(“모듈로 정밀도”)는 정밀도와 이진, 8진 또는 16진 표시 형식 중 하나를 사용하여 정수를 형식화할 때 선택적인 “모듈로” 플래그로 허용되어야 합니다(2의 거듭제곱인 진법). 먼저 % 연산자를 사용하여 정수를 range(base ** precision)으로 줄입니다. 그 결과는 정밀도와 정확히 같은 자릿수를 사용하는 예측 가능한 2의 보수 스타일 형식 지정입니다.
이 PEP는 “PEP 3101에서 정수 변환에 대한 정밀도는 무시된다”고 명시한 조항을 수정합니다.
근거
이진, 8진 및 16진수로 정수를 문자열 형식화할 때, 결과 문자열에 보장된 최소 자릿수가 포함되기를 원하는 경우가 많습니다. 알려진 기계어 폭의 범위를 갖는 부호 없는 정수(예: 8비트 바이트)의 경우, 이는 결과 자릿수가 정확히 그 수가 되는 경우도 많습니다. 이는 이전에 C 프로그래밍 언어의 방식과 밀접하게 관련된 . “정밀도” 형식 지정자를 사용하여 이전 스타일 % 형식에서 구현되었습니다.
>>> "0x%.2x" % 15
'0x0f' # two hex digits, ideal for displaying an unsigned byte
>>> "0o%.3o" % 18
'0o022' # three octal digits, ideal for displaying a umask or file permissions
PEP PEP 3101의 새 스타일 서식이 처음 도입되었을 때, str.format과 f-문자열에 사용되는 format specification은 충분히 단순했으므로 “precision”의 동작을 width 서식 지정자로 쉽게 모방할 수 있었습니다. 따라서 정밀도는 구현되지 않은 채로 남았으며 int 필드에서는 금지되었습니다. 그러나 시간이 지나면서 width와의 상호 작용이 정밀도를 모방하는 동작에서 눈에 띄게 벗어나는 새로운 형식 지정자들이 추가되었으므로, 정밀도를 자체 형식 지정자인 .로 다시 허용하는 것이 충분히 타당합니다.
width 형식 지정자는 형식화된 정수의 자릿수만이 아니라 전체 대체 필드 길이의 최솟값을 보장합니다. 예를 들어 해당 표시 형식의 접두사를 앞에 붙이는 훌륭한 # 지정자는 width를 차지합니다.
>>> x = 12
>>> f"0x{x:02x}" # manually specifying '0x' prefix
'0x0c' # two hex digits :)
>>> f"{x:#02x}" # use '#' format specifier to output '0x' automatically
'0xc' # only one hex digit :(
>>> f"{x:#08b}"
'0b001100' # we wanted 8 bits, not 6 :(
접두사의 길이는 항상 2로 알려져 있으므로, 원하는 자릿수에 2를 더해 수동으로 반영할 수 있다고 주장할 수도 있습니다. 그러나 이것이 나쁜 생각인 이유를 보여 주는 다음 예를 살펴보십시오.
- 두 번째 예를
f"{x:#04x}"로 수정하면, 얼핏 보기에 16진수 네 자리를 생성할 것처럼 보이지만 실제로는 두 자리만 생성합니다. 이는 가독성에 좋지 않습니다. 따라서4는 지나치게 ‘매직 넘버’이며,f"{x:#0{2+2}x}"처럼 지나치게 명시적으로 작성하여 이를 보완하려는 시도는 우스꽝스러워 보입니다. - 향후 길이가 2가 아닌 접두사를 사용하는 형식 지정자가 추가될 수도 있으며, 이 경우 Python의 내부 문자열 형식화 코드가 이를 자동으로 처리하는 대신 프로그래머가 접두사 길이를 계산해야 합니다.
sign형식 지정자를 사용하면 상황은 더 복잡해지며,f"{x: #0{1+2+2}x}"를 사용해야' 0x0c'가 생성됩니다.grouping_option을 도입하면 상황은 더욱 복잡해집니다. 예를 들어 정수를_로 연결된k개의 ‘word’ 세그먼트로 형식화하려면x = 3735928559; k = 2; f"{x: #0{1+2+4*k+(k - 1)}_x}"를 사용해야' 0xdead_beef'가 생성됩니다. 이를 정밀도를 사용하여f"{x: #_.8x}"로 작성하는 편이 분명 더 쉽지 않겠습니까?
이 시점에서 int 필드에 정밀도를 구현하여 얻을 수 있는 복잡성 감소가 모든 사용자에게 유익하다는 점은 분명합니다. 또한 이 제안은 int 필드만을 위해 독점적으로 요구되는 새로운 특수 사례 동작도 아닙니다. 정밀도 토큰 .은 이미 PEP 3101의 규정에 따라 str 데이터의 필드 길이를 자르고, float 데이터의 소수점 뒤 자릿수를 고정된 수로 보장하도록 구현되어 있습니다. 예를 들어 f"{0.1+0.2: .4f}"는 ' 0.3000'을 생성합니다. 따라서 이 제안으로 인해 format specification에 새로운 토큰을 추가할 필요가 없으며, 적당한 크기를 유지할 수 있습니다.
완결성을 위해, 합리적인 이의가 없으므로 정밀도가 십진법, 즉 밑 10에서도 작동하도록 제안합니다. 명시적으로, PEP 3101에 제시된 정수 표시 유형 중 정밀도 구현이 허용되는 것은 'b', 'd', 'o', 'x', 'X', 'n', 그리고 '' (None)입니다. 허용되지 않는 유일한 표시 유형은 c (‘문자’)입니다. 이 유형은 정수를 단일 유니코드 문자 또는 인쇄할 수 없는 문자에 대한 적절한 대체 문자로 형식화하기 위한 것이므로, 정밀도를 구현하는 것이 의미가 없습니다. 향후 'B' 및 'O'와 같은 새로운 정수 표시 유형이 추가되고, 필요한 변경을 가하면 'X'와 동일한 동작(즉, 대문자로 된 접두사와 숫자)을 제공할 수 있다면, 이러한 유형을 추가할 때 정밀도를 구현할지 여부를 적절히 고려해야 합니다. 여기에서 설명한 'B' 및 'O'의 경우에는 정밀도를 구현하는 것이 올바릅니다. 유효하지 않은 정수 표시 유형에 정밀도를 사용하려고 하면 ValueError를 발생시켜야 합니다.
음수에 대한 정밀도
지금까지 이 PEP에서는 정밀도를 적용한 음수의 형식화에 관해 신중하게 언급을 피해 왔지만, 이제 이를 논의하겠습니다.
간단한 결론
두 가지 동작을 원하므로, 후자의 동작을 켜고 끄는 z 플래그의 구현을 제안합니다.
z플래그 없이 정밀도를 적용하는 경우, 음의 정수x는 음수 부호와-x의 형식화 결과에 해당하는 숫자로 형식화해야 합니다. 이는 기존 스타일의%형식화와 동일하게 사용자에게 친화적인 동작입니다.예를 들어
f"{-12:#.2x}"는'-0x0c'를 생성해야 하며, 이는"%#.2x" % -12와 동일합니다.z플래그와 함께 정밀도를 적용하는 경우,f"{x:z.{n}{base_char}}"를 형식화할 때 먼저r = x % base ** n을 구하고,r을 정밀도 처리에 전달합니다. 그 결과 문자열은f"{r:.{n}{base_char}}"와 동일합니다.r은range(base ** n)에 속하므로 자릿수는 항상 정확히n개가 되며, 예측 가능한 2의 보수 방식의 형식화 결과가 생성됩니다. 이는struct와 같이 머신 너비 지향 정수를 다루는 환경의 최종 사용자에게 유용합니다.예를 들어
f"{-1:z#.2x}"를 형식화할 때-1은255 = -1 % 256을 통해256으로 나눈 나머지로 축약됩니다. 그 결과 문자열은f"{255:#.2x}"와 동일하며, 이는'0xff'입니다.z플래그는 2의 거듭제곱인 밑에 해당하는 표시 유형에만 구현해야 하며, 현재로서는 이진수, 8진수 및 16진수에 해당합니다. 정수를 10의 거듭제곱으로 나눈 나머지로 축약하는 것은 계산상 가능하지만, ‘10의 보수?’에 대한 수요가 없으므로 십진수 표시 유형에는 정밀도를 구현하지 않습니다.z플래그는 음수뿐만 아니라 모든 정수에 대해 작동해야 합니다.z라는 구문을 선택한 것은 다시 format specification의 적당한 크기를 유지하기 위한 배려입니다.z는 PEP 682에서float및Decimal유형의 음의 0을 양의 0으로 정규화하는 플래그로 형식 사양에 도입되었습니다. 현재int유형에는 구현되어 있지 않으며, 정수에는 ‘음의 0’ 상황이 결코 발생하지 않으므로z를 다시 플래그로 용도를 변경하는 데 논란의 여지가 없어 보입니다. 눈을 가늘게 뜨고 보면z는 2의 보수의2처럼 보입니다!
심층 고찰
먼저 2의 보수에서 부호 있는 정수의 이진 표현에 관한 몇 가지 관찰을 제시합니다. 이는 음수를 형식화하는 몇 가지 대안적인 방식으로 이어집니다.
부호 있는 수의 이진 표현에서 선행 숫자를 접두사로 확장하면 언제나 그 표현을 확장할 수 있다는 점에 주목하십시오.
45 (8-bit) 00101101
45 (9-bit) 000101101
-19 (8-bit) 11101101
-19 (9-bit) 111101101
음이 아닌 수에서는 이것이 자명합니다. 음수에서 그런 이유는 n비트 표현의 기존 선행 열이 -2 ** (n-1)의 값을 갖던 것에서 +2 ** (n-1)의 값을 갖게 되고, 값이 -2 ** n인 새로운 n+1번째 열이 앞에 추가되더라도 전체 합은 변하지 않기 때문입니다.
자릿수를 2의 거듭제곱으로 처리하는 C의 printf가 바로 이렇게 동작합니다.
printf("%#hhb\n", -19); // 0b11101101
printf("%#hho\n", -19); // 0355
printf("%#hhx\n", -19); // 0xed
printf("%#b\n", -19); // 0b11111111111111111111111111101101
printf("%#o\n", -19); // 037777777755
printf("%#x\n", -19); // 0xffffffed
반대로, 부호 있는 수의 이진 표현을 손실 없이 잘라내어 음이 아닌 경우에는 선행 0 하나만, 음수인 경우에는 선행 1 하나만 남길 수 있다는 점은 분명합니다.
45 (8-bit) 00101101
45 (7-bit) 0101101
-19 (8-bit) 11101101
-19 (7-bit) 1101101
이 예에서 숫자를 하나 더 잘라내면 둘 다 101101이 됩니다. 6개의 이진 숫자만 사용할 때 45와 -19는 모두 2 ** 6 = 64를 법으로 같은 값이므로 서로 구별할 수 없습니다. 따라서 부호 있는 정수 x를 최종 사용자에게 표시할 이진 문자열로 손실 없이 모호하지 않게 표현하기 위해, 사실상 ‘최소 너비’ 표현 규칙을 사용합니다. 즉 n개의 숫자를 사용하며, n은 x가 range(-2 ** (n-1), 2 ** (n-1))에 속하도록 하는 가장 작은 정수입니다.
8진수 및 16진수 문자열을 렌더링하려면 충분히 모호하지 않도록 ‘최소 너비’ 표현 규칙의 정의를 확장해야 합니다. 383의 최소 너비 이진 문자열은 0101111111이고, -129의 문자열은 전자의 접미사인 101111111입니다. 순진하고 잘못된 16진수 문자열 포맷팅 구현은 두 이진 표현을 000101111111로 padding하여 둘 다 '0x17f'로 렌더링합니다. 이 메서드가 이진 자릿수의 개수(12)를 밑의 비트 수(밑이 16일 때 4비트)로 나누어떨어지도록 하여 이진 표현을 (16진수) 자릿수로 분할할 수 있게 하려 한 것은 옳았지만, padding한 것은 잘못이었습니다. 앞서 살펴본 것처럼 이 메서드는 대신 extended했어야 합니다. 383은 000101111111로 확장되고, -129는 111101111111로 확장되므로, 383은 '0x17f'로, -129는 '0xf7f'로 렌더링됩니다.
따라서 ‘최소 너비’ 표현 규칙의 일반화된 정의는 다음과 같습니다. 정수 x를 밑 base로 렌더링할 때 n개의 자릿수를 생성하며, 여기서 n은 x가 range(-base ** n / 2, base ** n / 2)에 속하도록 하는 가장 작은 정수입니다.
이는 거부된 대안으로 이어집니다.
거부된 대안
z의 동작
2의 보수 방식 포맷팅 플래그인 z의 바람직한 구현을 두고, 정보 손실 없는 표현과 정보 손실 표현 중 어느 쪽인지에 대한 의견이 갈려 두 개의 주요 진영으로 나뉘었습니다. 정보 손실 없는 진영은 정수에 대응하는 포맷된 문자열이 서로 모두 달라야 하며, 최소 너비 표현 규칙을 통해 고유성이 보존되어야 한다고 생각합니다. z가 활성화된 경우의 정밀도도 z가 없을 때와 마찬가지로 요청된 자릿수의 최소 개수여야 한다고 생각합니다. 정보 손실 진영은 z가 활성화된 경우 정밀도가 먼저 모듈러 연산을 사용하여 정수를 축소해야 하며, 그러면 최소 너비 표현 문자열을 왼쪽에서 잘라내는 것과 동등하게 요청된 자릿수를 정확히 생성한다고 생각합니다.
다음 절에서 전자의 진영인 정보 손실 없는 포맷팅에는 사용 사례가 없으므로 거부된 아이디어라고 결론 내리고, 이에 따라 이 PEP에서는 후자의 정보 손실 동작을 제안하고자 합니다.
최소 너비 표현 규칙
이 아이디어는 정보 손실이 없는 동작 때문에 강력하게 고려되었지만, 모든 후보 사용 사례에서 사용 편의성을 저해하는 장애물입니다. 문자열 렌더링의 미학에 관한 이러한 주장은 비이성적이거나 개인적 취향에 관한 것이 아니라, 최종 사용자에게 정보가 전달되는 방식에서 중요한 요소입니다.
정수의 부호 여부를 전달하는 것이 중요한 프로그램에서는 일반 사용자가 음수 기호 -를 보기를 기대하므로 z의 어떤 구현도 사용해서는 안 됩니다. 최소 너비 표현 규칙을 사용하는 대안에서는 음수가 존재할 때마다 밑 범위의 상위 절반에 속하는 수의 선행 자릿수(1은 이진수, 4-7은 8진수, 8-f는 16진수)를 불편할 정도로 주의 깊게 찾아야 합니다. 이러한 사실상의 규칙을 모르는 최종 사용자는 물론이고, 알고 있더라도 프로그램에 이 규칙이 적용될 것이라고 예상하지 못한 사용자도 다음과 같은 상황을 겪게 됩니다.
f"{x:z#.2x}"를 사용하여 128과 -128을 포맷팅하면 각각 '0x080'과 '0x80'이 생성됩니다. PEP 작성자는 정상적인 조건에서 '0x80'을 음수 128로 읽을 가능성이 0%라고 생각합니다. 더욱이 양수 128을 '0x080'으로 끔찍하게 렌더링하는 것은 부호 있는지 여부와 무관하게 바이트의 간격이 일정한 헥스덤프를 생성해야 하는 프로그램에 쓸모가 없습니다. 모든 바이트는 '0xNN' 형식으로 렌더링되어야 합니다. 모듈러 정밀도가 부호와 무관한 올바른 방식으로 바이트를 처리하는 방법은 examples섹션을 참조하십시오.
따라서 반대로 말하면 z의 목적은 부호 여부가 중요하지 않은 환경에서 사용하는 것이며, 고정된 레지스터 크기의 2의 보수 하드웨어에서 발생하는 모듈러 산술에 따라 정수를 취급하는 것이 권장되기까지 하는 환경에서 사용될 가능성이 더 높습니다. 앞의 예에서 128과 -128은 256을 법으로 동치이며, 적절한 렌더링은 '0x80'입니다. 일반적으로 z의 목적은 base ** precision을 법으로 정수를 서로 같은 것으로 취급하는 것입니다. 마찬가지로 255와 -1은 각각 '0x0ff'와 '0xff'가 아니라 둘 다 '0xff'로 렌더링되어야 합니다. 잘라내기는 방해 요소가 아니라 의도된 동작입니다. 형식적으로 말하면 포맷팅은 Z/(base ** precision)Z의 동치류와 precision개의 자릿수를 가진 문자열 사이의 잘 정의된 전단사여야 합니다.
남은 질문은 사실상 왼쪽에서 잘린 문자열에서 발생하는 ‘정보 손실’에 대한 우려로서 “이 잘라내기를 사용자에게 전달할 방법은 없는가?”입니다. 하드웨어를 인식하는 정수와 그렇지 않은 정수라는 두 경우를 고려하면 의도하지 않은 정보 손실이 발생하는 경우가 애초에 존재한다는 이 질문의 전제를 받아들일 수 없습니다.
하드웨어를 인식하는 정수와 관련하여 지금까지 바이트의 부호 있는 범위와 부호 없는 범위의 합집합인 range(-128, 256)에 속하는 정수의 예를 살펴보았습니다. x와 x - 256을 동일하게 포맷팅하는 것의 장점은 분명히 확립되어 있습니다. z를 발견할 것으로 예상되는 이러한 맥락에서 해당 범위를 벗어나는 바이트에 대응하는 잘못된 정수는 프로그래밍 오류일 가능성이 높습니다. 예를 들어 라이브러리가 픽셀 밝기 정수를 257로 설정하고 f"{x:z#.2x}"를 통해 '0x101' 대신 '0x01'을 출력한다면, 이는 우리의 문제도 책임도 아닙니다. 문자열 포맷팅은 예외를 발생시켜서는 안 되며, 잘못된 이스케이프 시퀀스 "\y"의 경우처럼 SyntaxWarning조차 발생시켜서는 안 됩니다. 정수를 bytes([257])을 통해 직렬화하려고 할 때 bytes가 ValueError: bytes must be in range(0, 256)를 발생시키기 때문입니다. 적절한 ‘레이어’의 코드가 예외를 발생시키도록 하십시오. 이는 문자열 포맷팅이 아니라 라이브러리의 결함을 더 잘 나타내기 때문입니다.
하드웨어를 인식하지 않는 정수의 경우에는 의도적으로 z를 사용하도록 선택해야 하며, 이때 모듈러 산술이 의도한 효과로 선택됩니다. 이러한 이유로 range(-base ** precision / 2, base ** precision)의 범위를 벗어나는 정수에 대해서는 SyntaxWarning이나 ValueError를 발생시키지 않습니다.
따라서 모듈로 정밀도로 구현된 z의 손실 동작을 옹호했으며, 무손실 동작의 합리적인 사용 사례를 모두 소진했습니다.
고려한 후 거부할 마지막 절충안은 z를 .에 의존하는 플래그가 아니라 .와 결합할 수 있는 플래그로 구현하는 것입니다. 구체적으로 말하면, .없는 z는 형식화된 정수의 최소 너비 표현을 표시하도록 2의 보수 모드를 켜고, z없는 .는 이미 설명한 대로 크기에 대한 최소 자릿수와 필요한 경우 부호를 포함하는 정밀도를 구현하며, .와 결합된 z는 왼쪽을 잘라 내는 모듈로 정밀도를 켭니다. 이러한 조합의 미로는 누구에게도 유용해 보이지 않습니다. 이미 최소 너비 표현 관례의 사용 편의성을 불신하게 되었으므로 z는 단독으로 거의 사용되지 않을 것이며, 각각 최소 자릿수를 표시하는 두 옵션이 결합되어 정확한 자릿수를 표시하는 이 동작은 직관에 어긋나기 때문입니다.
무한 길이 표시
인기가 더 낮아 거부된 또 다른 대안은 z가 음이 아닌 수와 음수 앞에 각각 오는 0s 또는 1s의 무한한 접두부를 직접 인정하도록 하는 것이었습니다. 예를 들면 다음과 같습니다.
>>> f"{-1:z#.8b}"
'0b[...1]11111111'
>>> f"{300:z#.8b}"
'0b[...0]100101100'
이는 사실상 ‘무한한’ 접두부가 추가된 최소 너비 표현 관례입니다.
C 프로그래밍 언어에서 정밀도가 적용된 int 데이터의 머신 너비 종속 2의 보수 형식화는 음수에서 비롯되는 과도하게 긴 접두부를 표시하며, 작은 크기의 음수에서도 그러합니다.
printf("%#.2x\n", -19); // 0xffffffed
printf("%#.2llx\n", (long long unsigned int)-19); // 0xffffffffffffffed
최대 머신 너비에 의해 제한되지 않았다면 이 접두부는 무한히 계속될 수 있습니다!
Python의 int 형식은 실제로 최대 머신 너비의 제한을 받지 않습니다. 따라서 무한히 긴 2의 보수 문자열이 출력되는 것을 피하려면, 자기 자신을 포함하는 리스트를 출력할 때 내장 list의 문자열 형식화와 유사한 접근법을 사용할 수 있습니다.
>>> l = []
>>> l.append(l)
>>> l
[[...]]
>>> y = -1
>>> f"{y:z#.8b}"
'0b[...1]11111111'
예를 들어 -1 & x가 항상 자명하게 x와 같다는 것, 또는 수의 부호 반전의 이진 표현을 해당 수의 비트별 보수에 1을 더하여 얻을 수 있다는 것을 보여 줌으로써 비트 단위 이진 연산의 작동 방식을 초보자에게 교육하는 데 유용했을 수 있습니다.
>>> x = 42
>>> f"{x:z#.8b}"
'0b[...0]00101010'
>>> f"{~x:z#.8b}"
'0b[...1]11010101'
>>> f"{x|~x:z#.8b}"
'0b[...1]11111111'
# x | ~x == -1
# x | ~x == x + ~x because of their disjoint bitwise representations
# thus x + ~x == -1
# thus -x == ~x + 1
>>> y = ~x + 1
>>> f"{y:z#.8b}"
'0b[...1]11010110'
>>> y == -x
True
사용 사례가 지나치게 좁으며, 모듈로 정밀도가 이를 능가합니다.
일반 사항
- 1의 보수나 다른 이진 표현은 어떻습니까?
2의 보수가 매우 지배적이므로 다른 표현을 실제로 고려하는 사람은 아무도 없습니다. GCC는 2의 보수만 지원합니다.
- 아무것도 하지 않으면 어떻습니까?
프로그래머들은 정밀도를 흉내 내기 위해 임시방편으로 수정하면서
width형식 지정자를 사용해 계속 어렵게 작업하고 있습니다. 이는 용납할 수 없으며, 이 PEP의 근거는 정밀도의 추가 및 구현 선택에 대해 결정적인 주장을 제시합니다..를 사용하여 정수 필드에 정밀도를 구현하지 않으면 향후 사용을 위해.를 남겨 두게 됩니다. 그러나 PEP 3101 이후 약 20년의 기간 동안 어떠한 대안도 받아들여지지 않았으며,.의 다른 용도는 이전 스타일의%형식화 및 C 프로그래밍 언어와 더욱 동떨어지게 만듭니다.
구문
- 모듈로 정밀도를 사용하는 정밀도에
!를z.대신 사용하며,.와는 상호 배타적입니다.장점:
!는 그래픽적으로.와 관련되어 있으며, 말하자면 확장입니다. 모듈로 정밀도 플래그가 설정된 정밀도는 실제로 정밀도의 확장입니다.- 영어에서
!는 명령문에 자주 사용됩니다. 마찬가지로 모듈로 정밀도는 입력값이 형식 지정될 정확한 자릿수를 지정하는 반면, 정밀도는 최소 자릿수를 지정합니다. 이는 관용적인 표현입니다. !는z.와 달리 기호 하나만 사용합니다. 여기에!가.와 상호 배타적이라는 점까지 고려하면, 모듈로 정밀도를 활성화해도 작성하는 코드의 전체 길이는 변하지 않습니다.- 새로운
!기호를 사용하면z를 향후 다른 용도로 사용할 수 있도록 남겨 둘 수 있습니다.
단점:
z.는.에서 확장된다는 의미와.에 연결된 플래그라는 의미도 전달하며, 사전식 순서로 왼쪽에서 오른쪽으로 ‘모듈로’(z), ‘정밀도’(.")의 흐름을 따릅니다..와!가 서로 상호 배타적이므로, 초보 프로그래머는 format specification 문서를 볼 때 어느 것을 선택해야 할지 분석 마비에 빠질 수 있습니다.!는 단일 목적을 위한 형식 사양의 또 다른 추가 요소가 됩니다.str,float또는 다른 어떤 타입에도 구현되지 않습니다.- 또한 PEP 3101에 규정된 format string syntax에는 이미
["!" conversion]“명시적 변환 플래그”가 존재합니다. 예를 들어f"{s!r}"에서는!r가s에 대해repr을 호출합니다. 이는!형식 지정자와 구문상 충돌하지 않습니다. 형식 지정자[":" format_spec]은 명확하게 정의된 앞쪽 콜론으로 구분되기 때문입니다. 그러나 새로운 모듈러 정밀도 모드에 익숙하지 않은 사용자는!이 포함된 형식 문자열을 대충 보고 다른 동작을 예상할 수 있습니다.
결론:
- 그래픽적으로는 매력적이지만,
!는 기존z플래그를 오버로드하여 달성할 수 있는 단일 목적을 위해 형식 사양을 복잡하게 만듭니다.
하위 호환성
다음은 PEP 682에서 인용한 내용입니다:
새로운 형식 지정 동작은 선택적으로 활성화되므로 기존 프로그램의 숫자 형식 지정에는 영향을 주지 않습니다.
다만 현재처럼 .가 정수에 대해 ValueError를 발생시키는 것에 특별히 의존하는 경우는 예외입니다. 하지만 PEP 475를 인용하면 다음과 같습니다.
이 PEP의 저자들은 그러한 애플리케이션이 존재한다고 생각하지 않습니다.
예제 및 교육
정밀도
Python의 영향권에 있는 문서와 튜토리얼에서는 int필드의 형식을 지정할 때, 전체 대체 필드의 최소 길이가 아니라 최소 자릿수가 필요한 것이 분명한 경우 width대신 정밀도인 .를 기본 형식 지정자로 채택하도록 권장해야 합니다.
정밀도라는 개념은 C와 같은 다른 언어에서도 일반적이며 Python의 기존 % 형식 지정에도 이미 존재했으므로, 지나치게 과도하게 다룰 필요는 없지만, 아래의 적절한 몇 가지 예가 그 용도를 보여 줄 수 있습니다.
>>> def hexdump(b: bytes) -> str:
... return " ".join(f"{c:#.2x}" for c in b)
>>> hexdump(b"GET /\r\n\r\n")
'0x47 0x45 0x54 0x20 0x2f 0x0d 0x0a 0x0d 0x0a'
# observe the CR and LF bytes padded to precision 2
# in this basic HTTP/0.9 request
>>> def unicode_dump(s: str) -> str:
... return " ".join(f"U+{ord(c):.4X}" for c in s)
>>> unicode_dump("USA 🦅")
'U+0055 U+0053 U+0041 U+0020 U+1F985'
# observe the last character's Unicode codepoint has 5 digits;
# precision is only the minimum number of digits
모듈로 정밀도
모듈로 정밀도의 사용을 권장하기에 적합한 명확한 영역은 struct로 패킹하고 언패킹하는 것과 같은 머신 워드 폭 지향 정수를 다룰 때입니다. 부호 있는 정수와 부호 없는 정수를 일관되고 예측 가능하게 2의 보수 형식으로 지정하는 예제를 제시합니다.
>>> import struct
>>> my_struct = b"\xff"
>>> (t,) = struct.unpack('b', my_struct) # signed char
>>> print(t, f"{t:#.2x}", f"{t:z#.2x}")
'-1 -0x01 0xff'
>>> (t,) = struct.unpack('B', my_struct) # unsigned char
>>> print(t, f"{t:#.2x}", f"{t:z#.2x}")
'255 0xff 0xff'
# observe in both the signed and unsigned unpacking the modulo-precision flag 'z'
# produces a predictable two's complement formatting
참조 구현
이 PEP를 구현하는 풀 리퀘스트를 GitHub에서 확인할 수 있습니다: python/cpython#146437.
감사
다음 분들께 감사드립니다:
- 2의 보수 동작을 처음 제안해 주신 Raymond Hettinger에게 감사드립니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.