Extend divmod() for Multiple Divisors, by Thomas Bellman
This commit is contained in:
parent
f7322b8023
commit
74365f187c
|
@ -0,0 +1,181 @@
|
|||
PEP: 303
|
||||
Title: Extend divmod() for Multiple Divisors
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Thomas Bellman <bellman+pep-divmod@lysator.liu.se>
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Created: 31-Dec-2002
|
||||
Python-Version: 2.3
|
||||
Post-History:
|
||||
|
||||
|
||||
Abstract
|
||||
|
||||
This PEP describes an extension to the builtin divmod() function,
|
||||
allowing it to take multiple divisors, chaining several calls to
|
||||
divmod() into one.
|
||||
|
||||
|
||||
Specification
|
||||
|
||||
The builtin divmod() function would be changed to accept multiple
|
||||
divisors, changing its signature from divmod(dividend, divisor) to
|
||||
divmod(dividend, *divisors). The dividend is divided by the last
|
||||
divisor, giving a quotient and a remainder. The quotient is then
|
||||
divided by the second to last divisor, giving a new quotient and
|
||||
remainder. This is repeated until all divisors have been used,
|
||||
and divmod() then returns a tuple consisting of the quotient from
|
||||
the last step, and the remainders from all the steps.
|
||||
|
||||
A Python implementation of the new divmod() behaviour could look
|
||||
like:
|
||||
|
||||
def divmod(dividend, *divisors):
|
||||
modulos = ()
|
||||
q = dividend
|
||||
while divisors:
|
||||
q,r = q.__divmod__(divisors[-1])
|
||||
modulos = (r,) + modulos
|
||||
divisors = divisors[:-1]
|
||||
return (q,) + modulos
|
||||
|
||||
|
||||
Motivation
|
||||
|
||||
Occasionally one wants to perform a chain of divmod() operations,
|
||||
calling divmod() on the quotient from the previous step, with
|
||||
varying divisors. The most common case is probably converting a
|
||||
number of seconds into weeks, days, hours, minutes and seconds.
|
||||
This would today be written as:
|
||||
|
||||
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)
|
||||
|
||||
This is tedious and easy to get wrong each time you need it.
|
||||
|
||||
If instead the divmod() builtin is changed according the proposal,
|
||||
the code for converting seconds to weeks, days, hours, minutes and
|
||||
seconds then become
|
||||
|
||||
def secs_to_wdhms(seconds):
|
||||
w,d,h,m,s = divmod(seconds, 7, 24, 60, 60)
|
||||
return (w,d,h,m,s)
|
||||
|
||||
which is easier to type, easier to type correctly, and easier to
|
||||
read.
|
||||
|
||||
Other applications are:
|
||||
|
||||
- Astronomical angles (declination is measured in degrees, minutes
|
||||
and seconds, right ascension is measured in hours, minutes and
|
||||
seconds).
|
||||
- Old British currency (1 pound = 20 shilling, 1 shilling = 12 pence)
|
||||
- Anglo-Saxon length units: 1 mile = 1760 yards, 1 yard = 3 feet,
|
||||
1 foot = 12 inches.
|
||||
- Anglo-Saxon weight units: 1 long ton = 160 stone, 1 stone = 14
|
||||
pounds, 1 pound = 16 ounce, 1 ounce = 16 dram
|
||||
- British volumes: 1 gallon = 4 quart, 1 quart = 2 pint, 1 pint
|
||||
= 20 fluid ounces
|
||||
|
||||
|
||||
Rationale
|
||||
|
||||
The idea comes from APL, which has an operator that does this. (I
|
||||
don't remember what the operator looks like, and it would probably
|
||||
be impossible to render in ASCII anyway.)
|
||||
|
||||
The APL operator takes a list as its second operand, while this
|
||||
PEP proposes that each divisor should be a separate argument to
|
||||
the divmod() function. This is mainly because it is expected that
|
||||
the most common uses will have the divisors as constants right in
|
||||
the call (as the 7, 24, 60, 60 above), and adding a set of
|
||||
parentheses or brackets would just clutter the call.
|
||||
|
||||
Requiring an explicit sequence as the second argument to divmod()
|
||||
would seriously break backwards compatibility. Making divmod()
|
||||
check its second argument for being a sequence is deemed to be too
|
||||
ugly to contemplate. And in the case where one *does* have a
|
||||
sequence that is computed other-where, it is easy enough to write
|
||||
divmod(x, *divs) instead.
|
||||
|
||||
Requiring at least one divisor, i.e rejecting divmod(x), has been
|
||||
considered, but no good reason to do so has come to mind, and is
|
||||
thus allowed in the name of generality.
|
||||
|
||||
Calling divmod() with no divisors should still return a tuple (of
|
||||
one element). Code that calls divmod() with a varying number of
|
||||
divisors, and thus gets a return value with an "unknown" number of
|
||||
elements, would otherwise have to special case that case. Code
|
||||
that *knows* it is calling divmod() with no divisors is considered
|
||||
to be too silly to warrant a special case.
|
||||
|
||||
Processing the divisors in the other direction, i.e dividing with
|
||||
the first divisor first, instead of dividing with the last divisor
|
||||
first, has been considered. However, the result comes with the
|
||||
most significant part first and the least significant part last
|
||||
(think of the chained divmod as a way of splitting a number into
|
||||
"digits", with varying weights), and it is reasonable to specify
|
||||
the divisors (weights) in the same order as the result.
|
||||
|
||||
The inverse operation:
|
||||
|
||||
def inverse_divmod(seq, *factors):
|
||||
product = seq[0]
|
||||
for x,y in zip(factors, seq[1:]):
|
||||
product = product * x + y
|
||||
return product
|
||||
|
||||
could also be useful. However, writing
|
||||
|
||||
seconds = (((((w * 7) + d) * 24 + h) * 60 + m) * 60 + s)
|
||||
|
||||
is less cumbersome both to write and to read than the chained
|
||||
divmods. It is therefore deemed to be less important, and its
|
||||
introduction can be deferred to its own PEP. Also, such a
|
||||
function needs a good name, and the PEP author has not managed to
|
||||
come up with one yet.
|
||||
|
||||
Calling divmod("spam") does not raise an error, despite strings
|
||||
supporting neither division nor modulo. However, unless we know
|
||||
the other object too, we can't determine whether divmod() would
|
||||
work or not, and thus it seems silly to forbid it.
|
||||
|
||||
|
||||
Backwards Compatibility
|
||||
|
||||
Any module that replaces the divmod() function in the __builtin__
|
||||
module, may cause other modules using the new syntax to break. It
|
||||
is expected that this is very uncommon.
|
||||
|
||||
Code that expects a TypeError exception when calling divmod() with
|
||||
anything but two arguments will break. This is also expected to
|
||||
be very uncommon.
|
||||
|
||||
No other issues regarding backwards compatibility are known.
|
||||
|
||||
|
||||
Reference Implementation
|
||||
|
||||
Not finished yet, but it seems a rather straightforward
|
||||
new implementation of the function builtin_divmod() in
|
||||
Python/bltinmodule.c
|
||||
|
||||
|
||||
Copyright
|
||||
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
End:
|
Loading…
Reference in New Issue