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 1
else:
    return 0

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

>>> a = 13
>>> b = 12
>>> a > b
1
>>>

如果是这样的话:

>>> a > b
True
>>>

每次会少花一毫秒的时间思考打印出的 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 willpower
False = 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 也应该为真。那些刚了解布尔变量的新手可能会写:

    if x == True: ...
    

    而不是正确的形式:

    if x: ...
    

    许多人乍一看会对后一种形式感到不舒服,这在心理和语言上似乎有很强的理由,但是我认为解决办法应该是教育而不是削弱语言。

    毕竟,== 通常被视为传递符号,这意味着根据 a == b 和 b == c,可以推论出 a == c。但是,如果在一个数是真值的情况下,它与 True 进行比较的结果是相等的,则像 6 == True == 7 这样的暴行将成立,从而可以推断出错误的 6 == 7。那是不可接受的。(此外,它会破坏向后兼容性。但是,即使它不破坏,出于前面的原因,我仍然反对。)

    还应该提醒新手,没有理由写:

    if bool(x): ...
    

    因为布尔值隐含在“if”中。在这里,显式并 比隐式好,因为添加的词法会损害可重用性,并且限制了解释器的解释行为。(译注:”The Zen of Python“中认为”显式比隐式好“,但在这里,Guido 认为隐式更好,所以他在原文档中加粗了”not“)

    但是,有时候有理由写成:

    b = bool(x)
    

    当不需要保留对任意 x 对象的引用时,或者由于某些其它原因需要规范化时,这很有用。有时候这样写也很合适:

    i = int(bool(x))
    

    它将布尔值转换为整数的 0 或 1。传达了将该值用作 int 的意图。

实现

完整的 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

posted @ 2020-08-12 14:09  豌豆花下猫  阅读(649)  评论(0编辑  收藏  举报