PEP 303 – 여러 제수를 받도록 divmod() 확장
- Author:
- Thomas Bellman <bellman+pep-divmod at lysator.liu.se>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 31-Dec-2002
- Python-Version:
- 2.3
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 내장 divmod() 함수가 여러 제수를 받을 수 있도록 확장하여 여러 번의 divmod() 호출을 하나로 연결하는 방안을 설명합니다.
결정
이 PEP는 거부되었습니다. 연쇄 divmod()의 사용 대부분은 (예를 들어 기수 변환에서처럼) 상수 모듈러스를 수반하며, 루프로 작성하는 것이 더 적절합니다. 초를 일/시/분/초로 나누는 예는 월과 연으로 일반화되지 않습니다. 오히려 이 사용 사례 전체는 날짜 및 시간 모듈이 더 유연하고 견고하게 처리합니다. PEP에서 언급한 다른 사용 사례는 실제 코드에서 다소 드뭅니다. 이 제안은 명확성과 자명성 측면에서도 문제가 있습니다. 예제를 보면 인자 순서가 올바른지, 대상 튜플의 길이가 맞는지 바로 알기 어렵습니다. 다른 언어를 사용하던 사용자는 문서를 다시 읽지 않고도 표준적인 두 인자 형식을 이해할 가능성이 더 높습니다. 2005년 6월 17일의 python-dev 논의를 참조하십시오 [1].
명세
내장 divmod() 함수는 여러 제수를 받도록 변경되며, 시그니처가 divmod(dividend, divisor)에서 divmod(dividend, *divisors)로 바뀝니다. 피제수를 마지막 제수로 나누어 몫과 나머지를 얻습니다. 그런 다음 그 몫을 끝에서 두 번째 제수로 나누어 새로운 몫과 나머지를 얻습니다. 모든 제수를 사용할 때까지 이 과정을 반복하며, 그 후 divmod()는 마지막 단계의 몫과 모든 단계의 나머지로 이루어진 튜플을 반환합니다.
새로운 divmod() 동작의 Python 구현은 다음과 같을 수 있습니다.:
def divmod(dividend, *divisors):
modulos = ()
q = dividend
while divisors:
q, r = q.__divmod__(divisors[-1])
modulos = (r,) + modulos
divisors = divisors[:-1]
return (q,) + modulos
동기
때로는 제수를 바꿔 가며 이전 단계의 몫에 다시 divmod()를 호출하는 식으로 divmod() 연산을 연쇄적으로 수행하고 싶을 때가 있습니다. 가장 흔한 경우는 아마도 초 단위의 수를 주, 일, 시, 분, 초로 변환하는 것입니다. 현재는 이를 다음과 같이 작성합니다.:
def secs_to_wdhms(seconds):
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
w, d = divmod(d, 7)
return (w, d, h, m, s)
필요할 때마다 이렇게 작성하는 것은 번거롭고 실수하기 쉽습니다.
대신 제안에 따라 내장 divmod()를 변경하면, 초를 주, 일, 시, 분, 초로 변환하는 코드는 다음과 같이 됩니다.
def secs_to_wdhms(seconds):
w, d, h, m, s = divmod(seconds, 7, 24, 60, 60)
return (w, d, h, m, s)
이 코드는 입력하기 더 쉽고, 올바르게 입력하기도 더 쉬우며, 읽기도 더 쉽습니다.
다른 응용 분야는 다음과 같습니다.
- 천문학의 각도(적위는 도, 분, 초로 측정하고, 적경은 시, 분, 초로 측정합니다).
- 옛 영국 화폐(1파운드 = 20실링, 1실링 = 12펜스).
- 앵글로색슨 길이 단위: 1마일 = 1760야드, 1야드 = 3피트, 1피트 = 12인치.
- 앵글로색슨 무게 단위: 1롱톤 = 160스톤, 1스톤 = 14파운드, 1파운드 = 16온스, 1온스 = 16드램.
- 영국 부피 단위: 1갤런 = 4쿼트, 1쿼트 = 2파인트, 1파인트 = 20액량 온스.
근거
이 아이디어는 이러한 동작을 하는 연산자가 있는 APL에서 나왔습니다. (그 연산자가 어떻게 생겼는지는 기억나지 않으며, 어차피 ASCII로는 표현할 수 없을 것입니다.)
APL 연산자는 두 번째 피연산자로 리스트를 받지만, 이 PEP는 각 제수를 divmod() 함수의 개별 인자로 전달할 것을 제안합니다. 가장 흔한 사용 방식에서는 제수가 호출 안에 바로 상수로 들어갈 것으로 예상되고(위의 7, 24, 60, 60처럼), 괄호나 대괄호를 추가하면 호출만 어수선해지기 때문입니다.
divmod()의 두 번째 인자로 명시적인 시퀀스를 요구하면 하위 호환성이 심각하게 깨질 것입니다. divmod()가 두 번째 인자가 시퀀스인지 검사하게 만드는 것은 고려하기에도 너무 보기 흉한 것으로 간주됩니다. 그리고 다른 곳에서 계산된 시퀀스를 실제로 가지고 있는 경우에는, 대신 divmod(x, *divs)라고 쓰면 되므로 충분히 쉽습니다.
최소 하나의 제수를 요구하는 것, 즉 divmod(x)를 거부하는 것도 고려되었지만, 그렇게 할 만한 좋은 이유가 떠오르지 않았으므로 일반성을 위해 허용합니다.
제수 없이 divmod()를 호출해도 여전히 (원소가 하나인) 튜플을 반환해야 합니다. 그렇지 않으면, 가변 개수의 제수로 divmod()를 호출하여 원소 개수를 “알 수 없는” 반환값을 받는 코드는 그 경우를 특수 처리해야 할 것입니다. 제수 없이 divmod()를 호출한다는 것을 알고 있는 코드는 너무 어리석어서 특수 처리할 가치가 없는 것으로 간주됩니다.
제수를 반대 방향으로 처리하는 것, 즉 마지막 제수로 먼저 나누는 대신 첫 번째 제수로 먼저 나누는 것도 고려되었습니다. 그러나 결과는 가장 상위 부분이 맨 앞에, 가장 하위 부분이 맨 뒤에 오며(연쇄 divmod를 수를 가중치가 서로 다른 “자릿수”로 쪼개는 방법으로 생각해 보십시오), 제수(가중치)를 결과와 같은 순서로 지정하는 것이 합리적입니다.
다음과 같은 역연산도:
def inverse_divmod(seq, *factors):
product = seq[0]
for x, y in zip(factors, seq[1:]):
product = product * x + y
return product
유용할 수 있습니다. 그러나 다음과 같이 작성하는 것은
seconds = (((((w * 7) + d) * 24 + h) * 60 + m) * 60 + s)
연쇄 divmod보다 작성하기에도 읽기에도 덜 번거롭습니다. 따라서 이는 덜 중요한 것으로 간주되며, 그 도입은 별도의 PEP로 미룰 수 있습니다. 또한 그러한 함수에는 좋은 이름이 필요한데, PEP 작성자는 아직 적절한 이름을 생각해 내지 못했습니다.
문자열은 나눗셈도 나머지 연산도 지원하지 않지만, divmod("spam")을 호출해도 오류가 발생하지 않습니다. 그러나 다른 객체까지 알지 못하면 divmod()가 동작할지 여부를 판단할 수 없으므로, 이를 금지하는 것은 어리석어 보입니다.
하위 호환성
__builtin__ 모듈의 divmod() 함수를 교체하는 모듈은 새 문법을 사용하는 다른 모듈을 망가뜨릴 수 있습니다. 이런 경우는 매우 드물 것으로 예상됩니다.
인자 두 개가 아닌 다른 방식으로 divmod()를 호출할 때 TypeError 예외를 기대하는 코드는 깨질 것입니다. 이 역시 매우 드물 것으로 예상됩니다.
하위 호환성과 관련된 다른 문제는 알려진 바 없습니다.
참조 구현
아직 완성되지 않았지만, Python/bltinmodule.c에 있는 builtin_divmod() 함수를 새로 구현하는 꽤 간단한 작업으로 보입니다.
참고 문헌
Copyright
This document has been placed in the public domain.