PEP: 303 Title: Extend divmod() for Multiple Divisors Version:
$Revision$ Last-Modified: $Date$ Author: Thomas Bellman
<bellman+pep-divmod@lysator.liu.se> Status: Rejected Type: Standards
Track Content-Type: text/x-rst Created: 31-Dec-2002 Python-Version: 2.3
Post-History:

Abstract
========

This PEP describes an extension to the built-in `divmod()` function,
allowing it to take multiple divisors, chaining several calls to
`divmod()` into one.

Pronouncement
=============

This PEP is rejected. Most uses for chained `divmod()` involve a
constant modulus (in radix conversions for example) and are more
properly coded as a loop. The example of splitting seconds into
days/hours/minutes/seconds does not generalize to months and years;
rather, the whole use case is handled more flexibly and robustly by date
and time modules. The other use cases mentioned in the PEP are somewhat
rare in real code. The proposal is also problematic in terms of clarity
and obviousness. In the examples, it is not immediately clear that the
argument order is correct or that the target tuple is of the right
length. Users from other languages are more likely to understand the
standard two argument form without having to re-read the documentation.
See python-dev discussion on 17 June 2005 \[1\]\_.

Specification
=============

The built-in `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()` built-in 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`.

References
==========

.. \[1\] Raymond Hettinger, "Propose rejection of PEP 303 -- Extend
divmod() for Multiple Divisors"
https://mail.python.org/pipermail/python-dev/2005-June/054283.html

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:
