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 语言的人感到困惑。不经意间创建一个值错误的整数对象是极其容易的,因为对于 Python 语言本身而言,“013”表示“十进制 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) 的描述中删除“guess”一词。
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 兼容选项应该导致尝试使用旧八进制字面量时引发异常。
基本原理
关于这些问题的大部分讨论发生在 Python-3000 邮件列表上,始于2007年3月14日,起因是有人观察到,普通人会完全困惑于在数字字符串前添加“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 之间(包括 2 和 36)的显式基数,后一个数字与 ASCII 字母表中数字位数和相同大小写字母数的总和有可疑的算术相似性。
但是,最佳的纳入理由需要有实际用例来支持,因此支持所有基数的想法很快被否决,唯一剩下真正支持的基数是十进制、十六进制、八进制和二进制。
仅仅因为某个基数在邮件列表中有强烈支持者,并不意味着它真的应该出现在语言中,因此本节的其余部分是对这些特定基数与其它可能选择的实用性的论述。
人类不断使用其他数字进制。如果我告诉你现在是下午 12:30,我传达了定量信息,这可以说是由三种独立的进制(12、60 和 2)组成的,其中只有一种在上述“约定”列表中。但这些信息的**沟通**中,用于 12 进制和 60 进制的信息各使用了两位十进制数字,而且,反常地,本可以用一位十进制数字表示的信息却使用了两个字母。
因此,一般来说,人类通过名称(AM、PM、January 等)或通过十进制记数法来交流“正常”(非计算机)数值信息。显然,名称很少用于大量项目,因此十进制用于所有其他情况。有一些研究试图解释为什么会这样,通常得出阿拉伯数字系统非常适合人类认知的预期结论。[3]
在计算机设计的历史中,甚至有证据表明十进制记数法是计算机与人类交流的正确方式。第一批现代计算机之一 ENIAC [4] 以十进制进行计算,尽管当时已经有以二进制运行的计算机。
十进制计算机操作的重要性使得许多计算机,包括无处不在的 PC,都设计了用于操作“二-十进制编码”(BCD)[5]的指令,这是一种将 4 位分配给每个十进制数字的表示方法。这些指令的历史可以追溯到,当时对许多数字进行的最繁重的计算,实际上是执行文本 I/O 所需的计算。显示 BCD 无需对每个显示的数字执行除法/取余操作,这在大多数硬件不具备快速除法能力时是一个巨大的计算优势。促成使用 BCD 的另一个因素是,通过 BCD 计算,舍入会以人类执行的方式完全相同,因此尽管二进制在计算和存储方面具有优越性,BCD 在金融等领域仍有时被使用。
因此,如果不是因为计算机本身通常使用二进制进行高效计算和数据存储,整数的字符串表示可能总是十进制的。
不幸的是,计算机硬件不像人类那样思考,所以程序员和硬件工程师通常必须像计算机一样思考,这意味着 Python 必须能够以人类可理解的形式传达二进制数据。
二进制数据表示必须易于人类理解的要求意味着,每个符号应包含整数个二进制位(比特),同时在其他方面非常接近标准久经考验的十进制表示法(位置表示幂次,左侧表示更大的量级,字母表中符号不多等)。
这种二进制数据表示的明显“最佳选择”因此是八进制,它将阿拉伯数字字母表中选出的单个符号中尽可能多的整数位打包在一起。
事实上,一些计算机体系结构,例如 PDP8 和 8080/Z80,就是以八进制来定义的,即以三位为一组排列指令的位字段,并使用八进制表示来描述指令集。
即使在今天,八进制仍然很重要,因为存在位打包结构,例如 Unix 文件权限掩码,每个字段包含 3 位。
但八进制在用于较大数字时存在一个缺点。每个符号的位数虽然是整数,但它本身不是二的幂。这种限制(考虑到现在大多数计算机的字长是二的幂)导致了十六进制的出现,它比八进制更受欢迎,尽管它需要比十进制大 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”,以与“heXadecimal”的“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