写点什么

Python 为什么要在 18 年前引入布尔类型?且与 C、C++ 和 Java 都不同?

用户头像
Python猫
关注
发布于: 2020 年 09 月 05 日
Python 为什么要在 18 年前引入布尔类型?且与 C、C++ 和 Java 都不同?

花下猫语:在上一篇《Python 为什么能支持任意的真值判断? 》文章中,我们分析了 Python 在真值判断时的底层实现,可以看出 Python 在对待布尔值时,采用了比较宽泛的态度。官方对此是怎么考虑的呢?

下面的文章是我刚翻译的 PEP-285,作者是 Python 之父 Guido van Rossum。这个 PEP 意义非常重大,Python 的 bool 类型就是从它开始引入的,而我在上篇文章中分析到的很多问题,都能在这篇十几年前的文档中找到解释!另外它还回应了比较典型的一些争议,值得大家了解下。


PEP 原文 : https://www.python.org/dev/peps/pep-0285/


PEP 标题: PEP 285 -- Adding a bool type


PEP 作者: Guido van Rossum


创建日期: 2002-03-08


合入版本: 2.3


译者 :豌豆花下猫 @Python 猫公众号


PEP 翻译计划https://github.com/chinesehuazhou/peps-cn


概要


本 PEP 提议引入一个新的内置类型bool,它将包含两个常量FalseTrue。这个 bool 类型是 int 类型的直接子类型(在 C 中),并且在除了 repr() 和 str() 之外的大多数方面,它的值FalseTrue 都将表现得像是 0 和 1(例如,False == 0 和 True == 1 都为真)。


所有在概念上需返回布尔结果的内置操作,都将更改为返回 False 或 True,而不再是 0 或 1,例如,比较操作、“not”运算和 isinstance() 之类的断言方法。


评审


我已经收集了太多太多的反馈意见,因此我宣布:评审阶段(review period)正式结束。 我今天吃的是中国菜,我的签语饼上写着:“Strong and bitter words indicate a weak cause.” 它使我想起了一些反对本 PEP 的帖子... :-)


(译注:1、签语饼即 fortune cookies,这是一种美国文化特色。美国的中餐馆在结账的时候流行给客人一些写了签语的饼干,一般都是祝福语。2、那句签语出自维克多·雨果,意为:理亏者言辞激烈)


无论如何,这些是我的 BDFL 声明。(执行摘要(Executive summary):我不会更改任何内容;所有其它提议都会被拒绝。)


1、本 PEP 应该被接受吗?


=>是的。


有很多反对本 PEP 的观点。其中多数是出于误解。我已尝试在下面的 PEP 正文中澄清一些最常见的误解。对我而言唯一值得考虑的问题是新手们倾向于写“ if x == True”,但“if x”就足够了。下面也有更多关于它的信息。我认为这不足以拒绝本 PEP。


2、str(True) 应该返回“True”还是“1”?“1”可能会减少向后兼容性问题,但看起来很奇怪。(repr(True) 将始终返回“True”。)


=>“True”。


几乎所有评审人都同意这一点。


3、常量应该被命名为“True”和“False”(类似于 None)还是“true”和“false”(像 C++、Java 和 C99 那样)?


=>True 和 False。


大多数评审人都认为 Python 内的一致性要比跟其它语言的一致性更为重要。


4、是否应该通过适当的告警来消除对布尔值的非布尔运算,以便例如 True + 1 最终(在 Python 3000 中)变为非法的?


=>不该


有一小部分观点响亮的人,希望看到“教科书式”的布尔类型,即完全不支持算术运算,但大多数评审人都同意我,认为布尔类型应该支持算术运算。


5、operator.truth(x) 应该返回 int 还是 bool?


=>bool。


Tim Peters 认为应该返回一个整数,但是几乎所有其他评审人都认为应该返回一个布尔值。我的理由:operator.truth() 意味着强制其参数使用布尔类型上下文(它调用 C API PyObject_IsTrue())。无论结果是 int 还是 bool,都是次要的;如果有 bool,则没有理由不使用它。(在本 PEP 下,operator.truth() 成为了 bool() 的别名;这也可以。)


6、bool 应该继承自 int 吗?


=>是的。


在理想的情况下,bool 最好是实现为一种单独的整数类型,且支持执行混合的算术操作。但是,从 int 继承出 bool 将极大地简化实现(部分原因是,所有调用PyInt_Check() 的 C 代码都可兼容——它对于 int 的子类会返回 true)。


另外,我认为这符合可替换性(substitutability)概念:代码中需要 int 时,可以喂入 bool,它等同于 0 或 1。代码中需要 bool 时,若赋予 int,则可能不符合预期;例如,3&4 计算为 0,但是当 3 和 4 被视为真值时,却都为真。


7、是否应该改变“bool”的叫法?


=>不。


一些评审人主张使用 boolean 而不是 bool,因为这样更容易理解(新手可能听说过布尔代数(Boolean algebra),但可能对 bool 无感),或者因为他们讨厌缩写。


我的观点:Python 明智地运用缩写(例如'def'、'int'、'dict'),我不认为这会造成理解的负担。对于新手来说,无论它被叫作 waffle 还是 bool 都没关系;这只是一个新词,他们很快就能掌握它的含义。


(译注:waffle,我们一般熟知的意思是“华夫饼干”,但它还有个意思是“无意义的、无关紧要的、胡乱的话”)


一位评审人认为可以叫“truth”。我觉得这个叫法没有吸引力,实际上更倾向于保留该术语(在文档中),以指代在 Python 中已经存在的具体的真值概念。例如:“当将一个容器解释为一个 truth 值时,空容器会被视为假,而非空容器则被视为真”。


8、将来是否应该要求布尔运算符(例如“if”、“and”和“not”)使用一个布尔值作为参数,例如令“if []:”变为非法的,要求必须写成“ if bool([]):” ???


=>不!!!


有些人认为,这就是一门有教科书式布尔类型的语言应该的做法。因为它被提起了,所以其他人担心我可能会同意这一做法。


我来明确阐述对此的立场:这不是本 PEP 的动机,我也无意进行更改。(另请参见下面的“澄清”部分。)


基本原理


大多数语言最终都会发展出一个布尔类型,甚至 C99(新的改进版 C 标准,尚未广泛采用)也有一个。(译注:C99 标准诞生于 1999 年,本 PEP 写于 2002 年,时过境迁,如今 C99 标准基本上已是落伍的了)


许多程序员都觉得需要一种布尔类型,大多数 Python 文档因缺少布尔类型而含有歉意。我看过很多模块,它们在顶部定义了常量“False = 0”和“True = 1”(或类似的常量),并使用它们。


问题是每个人的做法都不一样。例如,你应该使用“FALSE”、“false”、“False”、“F”还是“f”呢?另外,假值应该为 0 或 None,或是一个其它的布尔类型打印出“true”或“false”呢?在语言中添加一个标准的布尔类型可以解决这些问题。


一些外部库(例如数据库和 RPC 相关的包)需要能够区分布尔值和整数值,尽管通常可以制定出解决方案,但如果语言本身提供了标准的布尔类型,则会更容易。这也适用于 Jython:某些 Java 类具有分别用于 int 和 boolean 参数的重载方法或构造函数。布尔类型可用于选择布尔变量。(显然,某些 COM 接口也是如此。)


标准的布尔类型(bool type)也可以作为强制将值解释为布尔值(Boolean)的方法,该方法可用于标准化布尔值。当一个布尔值需要归一化为两个值之一时,bool(x) 比“not not x”更清晰,也比这种写法更简洁:


if x:    return 1else:    return 0
复制代码

这是从传授 Python 中得出的一些经验。当向人们在交互式终端中展示比较运算符时,我认为这有点难看:


>>> a = 13>>> b = 12>>> a > b1>>>
复制代码

如果是这样的话:


>>> a > bTrue>>>
复制代码

每次会少花一毫秒的时间思考打印出的 0 或 1。


还有一个问题(它甚至困扰了曾经经验丰富但远离了 Python 一段时间的人):


>>> cmp(a, b)1>>> cmp(a, a)0>>>
复制代码

你可能会倾向于认为 cmp() 也返回一个布尔值,但实际上它可以返回三个不同的值(-1、0、1)。如果整数没有(通常)被用于表示布尔值结果,则这可以更加明显地表达出其它的含义。(译注:即只用 True/False 表示布尔值,则整数表达其它含义时就不会有歧义)


规范


以下 Python 代码详细列举了新类型的大多数属性:


class bool(int):    def __new__(cls, val=0):        # This constructor always returns an existing instance        if val:            return True        else:            return False    def __repr__(self):        if self:            return "True"        else:            return "False"    __str__ = __repr__    def __and__(self, other):        if isinstance(other, bool):            return bool(int(self) & int(other))        else:            return int.__and__(self, other)    __rand__ = __and__    def __or__(self, other):        if isinstance(other, bool):            return bool(int(self) | int(other))        else:            return int.__or__(self, other)    __ror__ = __or__    def __xor__(self, other):        if isinstance(other, bool):            return bool(int(self) ^ int(other))        else:            return int.__xor__(self, other)    __rxor__ = __xor__# Bootstrap truth values through sheer willpowerFalse = int.__new__(bool, 0)True = int.__new__(bool, 1)
复制代码

False 和 True 将是单例的(singletons),像 None 一样。因为这种类型有两个值,也许应该将它们称为“doubletons”?实际的实现将不允许创建 bool 的其它实例。


True 与 False 会被正确地序列化和打包,例如 pickle.loads(pickle.dumps(True)) 将返回 True, 而 marshal.loads(marshal.dumps(True)) 也一样。


所有在定义上需返回布尔结果的内置操作,都将更改为返回 False 或 True,而不再是 0 或 1。


具体而言,这会影响比较操作(<、<=、==、!=、>、>=、is、is not、in、not in),一元运算符'not',内置函数 callable()、hasattr()、isinstance() 和 issubclass() ,字典方法 has_key() ,字符串和 unicode 方法 endswith()、isalnum()、isalpha()、isdigit()、islower()、isspace()、istitle()、isupper() 和 startswith(),unicode 方法 isdecimal() 和 isnumeric(),以及文件对象的“closed”属性。operator 模块中的断言方法也被改为返回布尔值,包括 operator.truth()。


由于 bool 继承自 int,因此 True + 1 有效且等于 2,依此类推。这对于向后兼容性很重要:因为比较之类的操作当前返回整数值,所以无法确定现有应用程序怎么使用这些值。


预计随着时间的推移,标准库将在适当的时候更新为使用 False 和 True (但在以前允许使用 int 的场合,则不需要使用 bool 参数类型)。此更改不应引起在本 PEP 中未详细说明的其它问题。


C API


“boolobject.h”头文件为布尔类型定义了 C API。它包含在“Python.h”中,因此不需要再 include 它。


现有的名称 Py_False 和 Py_True 引用独一无二的布尔对象 False 和 True (之前,它们分别引用了值为 0 和 1 的静态整数对象,是众多整数之一)。


一个新的 API,即PyObject *PyBool_FromLong(long) ,会接收一个 C 长整型参数,并返回对 Py_False (当参数为零时)或 Py_True (当非零时)的新引用。


要检查对象是否为布尔对象,可以使用宏 PyBool_Check()。


布尔实例的类型是 PyBoolObject *。


布尔类型对象可作为 PyBool_Type 使用。


澄清


本 PEP 没有改变一个事实,即几乎所有类型的对象都可以用作真假值。例如,在 if 语句中使用时,一个空列表为 false,一个非空列表为 true;这不会改变,而且也不打算改变。


唯一改变的是在返回或赋值时,用于表示真假值的首选值。以前,这些首选的真假值是 1 和 0;本 PEP 将首选值更改为 True 和 False,并修改内置操作以返回这些首选值。


兼容性


因为要向后兼容,所以布尔类型拥有一些不严格的属性。例如,允许使用布尔参数进行算术运算,即将 False 视为 0,将 True 视为 1。而且,可以将 bool 用作序列对象的索引。


我不认为这是一个问题,也不希望朝这个方向发展语言。我认为,对“布尔性(Booleanness)”的更严格的解释不会使语言更清晰。


兼容性要求的另一个结果是表达式“True and 6”的值为 6,类似地,表达式“ False or None”的值为 None。


“and”和“or”运算符被设计来返回第一个决定了结果的参数,这点不会改变;特别地,它们不强制要求结果为布尔类型。当然,如果两个参数都是布尔值,那么结果肯定是一个布尔值。通过写“bool(x and y)”,也可以很容易地将其强制转成布尔类型。


解决了的问题


(另请参见上面的“评审”部分。)


  • 由于 bool 值的 repr() 或 str() 与 int 值不同,因此某些代码(例如,基于 doctest 的单元测试,以及可能依赖于 “%s”%truth 的数据库代码)可能会出错。解决这个问题很容易(无需显式引用 bool 类型),并且预计这只会影响非常少量的可以轻松修复的代码。


  • 其它语言(C99、C ++、Java)均以小写形式命名常量“false”和“true”。对于 Python,我更喜欢遵照现有内置常量的惯例,这些内置常量全部使用驼峰式命名:None 、Ellipsis、NotImplemented (以及所有的内置异常)。Python 内置的命名空间全部用小写字母表示函数和类型。


  • 前面提到过,为了满足用户的期望,对于在布尔上下文中被认为是真的每个 x,x == True 表达式都应该为真,同样,如果 x 被认为是假,则x == False 也应该为真。那些刚了解布尔变量的新手可能会写:


实现


完整的 C 实现代码已上传到 SourceForge 补丁管理器:https://bugs.python.org/issue528022


它将很快被合入到 python 2.3a0 的 CVS 中。


版权


本文档已进入公共领域。


源文档:https://github.com/python/peps/blob/master/pep-0285.txt


更多的 PEP 中文翻译内容,可在 Github 查阅:https://github.com/chinesehuazhou/peps-cn


发布于: 2020 年 09 月 05 日阅读数: 254
用户头像

Python猫

关注

Python猫作者,一个Python爱好者 2018.08.07 加入

历史文章会陆续搬运过来,欢迎大家在微信搜索“Python猫”,也可以找到我

评论

发布
暂无评论
Python 为什么要在 18 年前引入布尔类型?且与 C、C++ 和 Java 都不同?