PEP 3127 – 整数字面量支持与语法
- 作者:
- Patrick Maupin <pmaupin at gmail.com>
- 讨论列表:
- Python-3000 列表
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2007年3月14日
- Python 版本:
- 3.0
- 历史记录:
- 2007年3月18日
摘要
本 PEP 提出对 Python 核心进行更改,以使不同基数(进制)的整数的字符串字面量表示的处理方式合理化。这些更改针对 Python 3.0,但更改中向后兼容的部分应添加到 Python 2.6 中,以便所有有效的 3.0 整数字面量在 2.6 中也都是有效的。
该提案是
- 八进制字面量现在必须使用前导“0o”或“0O”指定,而不是“0”;
- 现在通过前导“0b”或“0B”支持二进制字面量;以及
- 将为字符串格式化中的二进制数字提供支持。
动机
本 PEP 受两个不同问题驱动
- 整数的默认八进制表示对不熟悉类 C 语言的人来说是隐含的混淆。由于“013”对 Python 语言本身来说意味着“十进制 11”,而不是“十进制 13”,因此很容易无意中创建具有错误值的整数对象,这与大多数人赋予此字面量的含义不同。
- 一些 Python 用户强烈希望在语言中支持二进制。
规范
语法规范
语法将发生更改。对于 Python 2.6,更改后的和新的标记定义将是
integer ::= decimalinteger | octinteger | hexinteger |
bininteger | oldoctinteger
octinteger ::= "0" ("o" | "O") octdigit+
bininteger ::= "0" ("b" | "B") bindigit+
oldoctinteger ::= "0" octdigit+
bindigit ::= "0" | "1"
对于 Python 3.0,“oldoctinteger”将不被支持,如果字面量具有前导“0”和第二个字符是数字,则会引发异常。
对于这两个版本,这都需要更改 PyLong_FromString 以及语法。
文档也必须更改:grammar.txt 以及参考手册中的整数字面量部分。
PEP 306 应检查其他问题,如果其中描述的过程不足,则应更新该 PEP。
int() 规范
int(s, 0) 也将匹配新的语法定义。
这应该会随着语法更改所需的 PyLong_FromString 的更改而自动发生。
此外,int() 的文档应更改为解释 int(s) 的操作与 int(s, 10) 相同,并且应从 int(s, 0) 的描述中删除“猜测”一词。
long() 规范
对于 Python 2.6,long() 的实现和文档应更改以反映新的语法。
词法分析器异常处理
如果无效标记包含前导“0”,则异常错误消息应比当前的“SyntaxError: invalid token”提供更多信息。它应该解释十进制数字不能有前导零,八进制数字需要在前导零之后添加“o”。
int() 异常处理
对于任何使用字符串调用 int() 的调用引发的 ValueError,至少应在错误消息中明确包含基数,例如
ValueError: invalid literal for base 8 int(): 09
oct() 函数
oct() 应更新为在八进制数字前面输出“0o”(对于 3.0 和 2.6 兼容模式)。
输出格式化
在 3.0 中,字符串 % 运算符的“o”选项的备用语法需要更新为在前面添加“0o”,而不是“0”。在 2.6 中,备用八进制格式将继续只添加“0”。在 2.6 和 3.0 中,% 运算符都不支持二进制输出。这是因为 PEP 3101(str.format)已经支持二进制输出,这是首选的字符串格式化方法。
从 2.6 到 3.0 的过渡
2to3 转换器必须将“o”插入任何八进制字符串字面量中。
Python 2.6 的 Py3K 兼容选项应导致尝试使用 oldoctinteger 字面量引发异常。
基本原理
关于这些问题的大多数讨论始于 2007 年 3 月 14 日,在 Python-3000 邮件列表中进行,起因是有人观察到,普通人在发现将“0”添加到一串数字前面会完全改变该数字串的含义时会完全感到困惑。
在此讨论期间,有人指出,关于该主题的类似但较短的讨论发生在 2006 年 1 月,起因是发现了同样的问题。
背景
出于历史原因,Python 在不同基数(进制)下整数的字符串表示(用于字符串格式化和标记字面量)大量借鉴了 C。 [1] [2] 使用表明,指定八进制数的历史方法令人困惑,并且最好能够额外支持二进制字面量。
在本文档中,除非另有说明,否则关于整数的字符串表示的讨论都与以下功能相关
- 字面量整数标记,由普通模块编译、eval() 和 int(token, 0) 使用。(此提案未修改 int(token) 和 int(token, 2-36))。
- 在 2.6 下,long() 的处理方式与 int() 相同
- 将整数格式化为字符串,无论是通过 % 字符串运算符还是新的 PEP 3101 高级字符串格式化方法。
假定
- 所有这些功能都应具有相同的一组支持的基数,以确保一致性。
- Python 源代码语法和 int(mystring, 0) 应继续共享相同的行为。
移除旧的八进制语法
本 PEP 提出,在 Python 3.0(以及 2.6 的 Python 3.0 预览模式)中,将删除通过使用前导零指定八进制数的功能,并且每当前导“0”紧跟另一个数字时,都会引发 SyntaxError。
在当前讨论期间,几乎所有人都一致同意
eval('010') == 8
不应该再成立,因为这会让新用户感到困惑。还建议
eval('0010') == 10
应该成立,但这更有争议,因为它与其他计算机语言中的用法非常不一致,因此可能会出现错误。
几乎所有当前流行的计算机语言,包括 C/C++、Java、Perl 和 JavaScript,都将以零开头的数字序列视为八进制数。主张将其视为十进制数的支持者有一个非常合理的观点——如下面的 支持的基数 中所讨论的,整个非计算机世界几乎完全使用十进制数。有大量轶事证据表明,许多人在遇到非十进制基数时会感到沮丧和困惑。
但是,在大多数情况下,大多数人不会在十进制数字前面写不必要的零。主要例外是当尝试对齐数字列时。但由于 PEP 8 特别不鼓励使用空格来尝试对齐 Python 代码,因此可以推测同样的论点也适用于出于相同目的使用前导零。
最后,尽管电子邮件讨论经常集中在是否有人实际使用八进制,以及我们是否无论如何都应该满足这些老用户,但这几乎完全无关紧要。
假设计算领域的罕见的新手确实偶尔或习惯性地使用前导零表示十进制数。Python 可以
- 像现在这样默默地对他们的数字做错事;
- 立即让他们明白这种语法是不可行的(是的,SyntaxWarning 应该比现在更温和,但这是另一个 PEP 的主题);或者
- 让他们继续认为计算机对以“0”开头的多位十进制整数很满意。
有些人热情地认为 (c) 是正确的答案,如果我们能够确定新用户永远不会成长和发展并开始编写 AJAX 应用程序,他们将绝对正确。
因此,虽然新 Python 用户可能会(目前)对延迟发现他们的数字无法正常工作感到困惑,但我们可以通过立即向他们解释 Python 不喜欢前导零(希望使用合理的消息!)来解决此问题,或者我们可以将这种教学体验委托给浏览器中的 JavaScript 解释器,并让他们在那里尝试调试他们的问题。
支持的基数
本 PEP 提出 Python 语言支持的基数将是 2、8、10 和 16。
一旦同意必须从语言中删除八进制(基数 8)整数表示的旧语法,下一个显而易见的问题是“我们是否真的需要一种方法来指定(和显示)八进制数?”
这个问题很快就会演变成“语言需要支持哪些基数?”由于计算机非常擅长执行您指示它们执行的操作,因此在讨论中一个诱人的答案是“所有基数”。这个答案显然以前也出现过——int() 构造函数将接受 2 到 36(含)之间的显式基数,后一个数字与 ASCII 字母表中数字位数和相同大小写字母的位数之和具有可疑的算术相似性。
但是,最好的包含论点将有用例来支持它,因此支持所有基数的想法很快就被拒绝了,并且唯一留下任何实际支持的基数是十进制、十六进制、八进制和二进制。
仅仅因为某个特定基数在邮件列表中有一个声音支持者,并不意味着它真的应该包含在语言中,因此本节的其余部分是对这些特定基数相对于其他可能选择的实用性的论述。
人类不断使用其他数字进制。如果我告诉你现在是下午 12:30,我传达了定量信息,可以说它由三个独立的基数(12、60 和 2)组成,其中只有一个在上面“约定”的列表中。但是,信息的传达使用两个十进制数字分别表示基数 12 和基数 60 的信息,并且,具有讽刺意味的是,使用两个字母表示本可以使用单个十进制数字表示的信息。
因此,总的来说,人类交流“普通”(非计算机)数字信息,要么通过名称(AM、PM、一月、……),要么通过使用十进制表示法。显然,对于大量项目,名称很少使用,因此其他所有情况都使用十进制。有一些研究试图解释为什么会这样,通常得出预期的结论,即阿拉伯数字系统非常适合人类认知。[3]
甚至在计算机设计史上也有证据表明,十进制表示法是计算机与人类交流的正确方式。第一批现代计算机之一,ENIAC [4] 使用十进制进行计算,即使当时已经存在使用二进制运算的计算机。
十进制计算机运算非常重要,以至于许多计算机,包括无处不在的PC,都设计了用于操作“二进制编码的十进制”(BCD)[5] 的指令,这种表示法将每个十进制数字分配 4 位。这些指令来自一个时代,当时许多数字上执行的最繁重的计算实际上是执行文本 I/O 所需的计算。可以显示 BCD,而无需对每个显示的数字执行除法/取余运算,当大多数硬件没有快速的除法功能时,这在计算上是一个巨大的胜利。导致使用 BCD 的另一个因素是,使用 BCD 计算时,舍入的方式与人类完全相同,因此尽管二进制在计算和存储方面具有优势,但 BCD 在金融等领域仍然有时使用。
所以,如果不是因为计算机本身通常使用二进制进行高效计算和数据存储,整数的字符串表示可能总是十进制的。
不幸的是,计算机硬件的思维方式与人类不同,因此程序员和硬件工程师必须经常像计算机一样思考,这意味着 Python 必须能够以人类可以理解的形式交流二进制数据。
二进制数据表示法必须易于人类认知处理的要求意味着它应该每个符号包含整数个二进制数字(位),同时在其他方面非常接近标准的久经考验的十进制表示法(位置表示幂,左侧为较大数量级,字母表中的符号不要太多等)。
因此,这种二进制数据表示法的明显“最佳选择”是八进制,它将尽可能多的整数位打包到从阿拉伯数字字母表中选择的单个符号中。
事实上,一些计算机架构,例如 PDP8 和 8080/Z80,都是根据八进制定义的,即按三组排列指令的位域,并使用八进制表示法来描述指令集。
即使在今天,八进制也很重要,因为它具有每字段 3 位的按位打包结构,例如 Unix 文件权限掩码。
但是,当用于较大的数字时,八进制有一个缺点。每个符号的位数,虽然是整数,但本身不是 2 的幂。考虑到(当今大多数计算机的字长都是 2 的幂),这个限制导致了十六进制,尽管它需要的字母表比十进制大 60%,但它比八进制更受欢迎,因为每个符号包含 4 位。
一些数字,例如 Unix 文件权限掩码,当以八进制表示时,人类很容易解码,但在十六进制中很难解码,而其他数字则更容易用十六进制处理。
不幸的是,计算机中使用的二进制数在十六进制或八进制中也无法很好地表达。值得庆幸的是,定期处理这些数字的人较少,但另一方面,这意味着讨论列表中的几个人质疑在 Python 中添加直接二进制表示的明智性。
一个非常有用的数字示例是读取和写入硬件寄存器。有时硬件设计师会避开人类可读性,并选择地址空间效率,通过将多个位域打包到未对齐位位置的单个硬件寄存器中,并且对于人类来说,重建由一个十六进制数字的上 3 位和下一个十六进制数字的下 2 位组成的 5 位字段既乏味又容易出错。
即使 Python 将二进制信息传达给人类的能力仅对一小部分技术人群有用,但正是这部分人群包含了 Python 核心团队的大多数成员(如果不是全部的话),因此,即使是最无用的表示法——直接二进制,在 Python 社区中也有一些热情的支持者,几乎没有坚定的反对者。
支持基数的语法
此提案建议使用“0o”前缀(使用大写或小写“o”)表示八进制,以及“0b”前缀(使用大写或小写“b”)表示二进制。
不支持大写字符得到了强烈的支持,但这在另一个 PEP 中是一个单独的主题,因为用于复数的“j”、用于指数的“e”和用于原始字符串的“r”(仅举几例)已经支持大写。
在 Python-3000 上的讨论中,用于分隔不同基数的语法受到了很多关注。这种语法有几个(有时是冲突的)要求和“锦上添花”的功能
- 对于输入语法和输出(例如字符串 % 运算符)语法,它应该尽可能地与其他语言和 Python 的先前版本兼容。
- 它应该尽可能地对普通观察者来说显而易见。
- 应该很容易直观地区分以不同基数格式化的整数。
建议的语法包括任意基数前缀,例如 16r100(十六进制的 256),以及类似于 100h 汇编程序风格后缀的基数后缀。关于字母“O”是否可以用于八进制的争论非常激烈——在某些字体中,大写“O”看起来与零非常相似。有人建议使用“c”(“oCtal”的第二个字母),甚至使用“t”代表“ocTal”和“n”代表“biNary”,以配合十六进制的“x”。
对于字符串 % 运算符,已经使用“o”来表示八进制。二进制格式化没有添加到 % 运算符中,因为PEP 3101(高级字符串格式化)已经支持二进制,% 格式化将在未来弃用。
归根结底,由于大写“O”可能看起来像零,而大写“B”可能看起来像 8,因此决定这些前缀应该只使用小写,但是,像原始字符串的“r”一样,这可能是一个偏好或风格指南问题。
未解决的问题
在讨论中有人建议,对于所有数字和字符串特殊修饰符,例如十六进制的“x”、原始字符串的“r”、指数的“e”和复数的“j”,都应该使用小写。这是一个适用于单独 PEP 的问题。
此 PEP 对输入中的大写或小写不作任何规定,只是注意到,为了保持一致性,如果不对其他字母的输入解析删除大写,则应为八进制和二进制添加大写,并在假设此更改下记录更改,因为还没有关于大小写问题的 PEP。
输出格式可能会有所不同——输出格式字符串中已经有很多大小写敏感性的先例,并且需要达成共识,即字符串 % 运算符的“备用形式”支持大写“B”或“O”字符用于二进制或八进制输出存在有效的用例。目前,PEP 3101 甚至不支持此备用功能,并且 hex() 函数不允许程序员指定“x”字符的大小写。
仍然有一些强烈的意见认为,在 Python 3.0 中应该允许“0123”作为字面十进制数。如果这是正确的做法,则可以在其他 PEP 中轻松涵盖这一点。此提案仅采取第一步,即使“0123”不再是有效的八进制数,原因在基本原理中有所说明。
2to3 转换器是否(或应该)有一个选项,该选项仅进行与 2.6 兼容的更改?是否应该在 2.6 发布之前在 2.6 库代码上运行它?
是否应该添加一个与 hex() 和 oct() 匹配的 bin() 函数?
一旦我们有了高级字符串格式化,hex() 是否真的那么有用?
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3127.rst
上次修改时间:2023-09-09 17:39:29 GMT