PEP 303 – 为多个除数扩展 divmod()
- 作者:
- Thomas Bellman <bellman+pep-divmod at lysator.liu.se>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2002-12-31
- Python 版本:
- 2.3
- 历史记录:
摘要
本 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()
函数进行了相当简单的重新实现。
参考资料
版权
本文件已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0303.rst
最后修改时间:2023-09-09 17:39:29 GMT