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
最后修改: 2025-02-01 08:59:27 GMT