25. None、True与False 的故事(兼解释字面量(literals)、关键字与内置变量)

Python关键字与内置名称

原文by Guido van Rossum 2013-11-10 翻译by kant


1. 缘起

最近,有人通过邮件向我提了一个有趣的问题:

关键字与字面量的区别何在?为什么 Python 3 中的 True 和 False 是关键字,而不是字面量? 我前段时间惊恐地发现,在 Python 2 中,我们可以给 True/False 赋值。于是我研究了下,发现在 PEP 285 中,True、False 与 None 一样,都是常量(constants)。Python 2.4 之后,禁止用户给 None 赋值,但在 Python 3 之前,对 True/False 的赋值一直是被允许的。是有什么特殊原因,让 None 一开始就作为变量而不是字面量吗?


2. 关键字 VS 字面量

先来回答第一个问题:关键字与字面量的区别。

在编程句法中,关键字也叫保留字,类似于这门语言中的识别器,或者从解析器的角度看,就像某种 token。识别器由字母、数字及下划线组成,但不能以数字开头(这是 Python 中的定义,其它语言,如 C和 Java 的定义也类似)。

关于关键字,最重要的一点是,它们不可以用作变量名称(方法名、类名等等)。大家比较熟悉的 Python 关键字包括 if、while、for、and、or 等等。

而字面量是一个常量的值的表示,常见的字面量包括数字(如 42,3.14,1.6e-10 等)和字符串(如 “Helloworld”)。解析器可以识别字面量,但识别的具体规则一般会很复杂。比如在 Python 3 中,以下都是数字字面量:


    ''123
    ''1.0
    ''1.
    ''.01e10
    ''.1e+42
    ''123.456e-100
    '' 0xfffe
    '' 0o755

而以下则不是:


    ''.   # 点
    ''e10  # 识别器
    ''0y12  # 字面量 0 加一个识别器 y12
    ''0xffe+10  # 字面量 0xffe 加一个加号以及数字 10

注意,字面量并不是常量。我们经常在代码中定义常量,如:


    ''MAX_LEVELS = 15

这里,15 是字面量,而 MAX_LEVEL 则是一个识别器,由于全部都是大写字母,因此,用户可能不会在代码中改变其值,也就是说,可能是一个常量——不过,常量中使用大写字母只是惯例而已,Python 解析器并不会因为变量名由大写字母组成就把它当做常量,也不强制所有常量都用大写字母表示。

但反过来写就会被解析器拒绝:


    ''15 = MAX_LEVELS

因为赋值操作符(=)的左边必须是一个变量,而字面量不可以作为变量名。(变量的准确定义非常复杂,有些看着像表达式的,其实也是变量,比如 d[k],(ab),foo.bar 等等——不过 f(),() 或 42 就不是变量。在 del 语句中,也使用同样的变量定义。)


3. 解析方式

接下来,我们看 None,True 和 False。

先来看 None,因为它在语言诞生之初就有了。(相对来说,True 和 False 是后期添加的,最初是在 Python 2.2.1 版本。)None 是一个单例对象(即语言中只有一个 None),表示值的缺失。例如,假设 d 是一个字典,如果 d 中有键 k 的话,d.get(k) 会返回 d[k] ,否则就返回 None。

早期版本中,None 只是 Python 的一个“内置名称”,解析器并不知道什么 None,正如它也不知道 int,float,str 等内置类型,或 KeyError 、 ZeroDivisionError 等内置异常。所有这些,对解析器而言都是识别器,在解析用户代码时,查找它们的过程与查找其它名称的过程是一样的(比如用户自己定义的方法或变量)。

在以下代码中,每一行都是同样解析,产生一样的解析树( = ):


    ''x = None
    ''x = int
    ''x = foobar

而下面的代码则会产生另一种解析树( = ):


    ''x = 42
    ''x = 'hello'

因为解析器处理数字、字符串等字面量的方式与处理识别器的方式不同。

结合这个例子与之前 MAX_LEVEL 的例子,我们知道,如果左右互换,前面三行代码是可以被解析器接受的( = ),而后面两行则不行( = 不成立)。


4. 内置名称 VS 关键字

这样设计的结果是:如果你想恶心使用你代码的人,可以给内置变量重新赋值,比如:


    ''int = float
    ''def parse_string(s):
    '' return int(s)
    ''print(parse_string('42'))   # 将打印42.0

有些人可能会说:“这有什么大不了的?正常程序员肯定不会这样写。”而另一些人则完全目瞪口呆:“怎么可能有这种允许赋值给内置名称的傻逼语言?!”

这个问题比较微妙,与一致性的保持和语言的发展历史有关。我相信,如果不查文档,你肯定写不出 Python 中的所有内置名称(至少我做不到),而且我也相信,很多人并不认识每一个内置名称。(想尝试的话,可以用 dir(builtins) 命令)

就以比较奇怪的内置名称 copyright、credits 与 license为例,由于它们的存在,Python 才能在每次打开交互窗口时发出问候语:


    ''Python 3.4.0a4+ (default:0917f6c62c62Oct 22 201310:55:35)
    '' [GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin
    '' Type \"help\" \"copyright\" \"credits\" or \"license\" for more information.
    '' >>> credits
    Thanks to CWI CNRI BeOpen.comZope Corporation and a cast of thousands
    for supporting Python development.  See www.python.org for more information.
    '' >>>

然而,是不是因为需要发出问候语,就禁止用户使用 credits 作为变量或参数名称呢?我觉得不然。显然,不少用户根本就没有意识到这些隐晦内置名称的存在,禁止他们使用这些词就会变得很奇怪。而这个问题并没有什么明确的界限。比如说,很多人在函数或方法中使用 str 、 len 、compile 或 format 作为参数名称。

另外,假如用户在 Python 2.5 的代码中使用 bytes 作为变量名称,而在 Python 2.6 中,bytes 已经是一个内置函数名(实际上是 str 的化名),那么,原来的代码是不是必须修改呢?恐怕也不合理。(即便在 Python 3 中,bytes 成为一种基本数据类型之后,依然可以作为变量名。)

另一方面,用户显然不能把 if 或 try 作为变量名称,因为它们是保留字(关键字),解析器处理的方式不一样。而且用户不管用不用得上,都必须记住这些保留字,才能避免使用它们。因此,我们得尽可能减少保留字的数量,每次增加关键字,核心开发者们都要斟酌犹豫很久。

事实上,很多新特性的提案,都因为需要引入新的关键字而被拒绝或调整了。当我们确定要添加一个新的关键字时,至少会提前一个版本发出 depreciation 警告。(另外,其实也有一个让开发者提前使用新关键字的方法,比如“from future import with_statement”。)

内置名称没有这个问题,碰巧使用了内置名称的代码依然是可以正常运行的(只要不在同一个函数中调用被重新赋值的内置名称)。添加内置名称时,我们还是比较保守的,但至少不用担心因为添加内置名称导致原有代码无法运行。

这种设计的唯一(轻微)代价就是,有时候有些人会利用这个机制来搞一些恶作剧。而一个人要写出烂代码有一万种方式,我不觉得这种设计是什么严重的问题。


5. None、True、False成为关键字(保留字)

讨论了这么多内置名称和关键字的区别后,再来看 None:为什么我们最终还是把 None 作为保留字?

坦白说,可能主要出于社会因素考虑。None 不像其它内置名称或异常,它在 Python 中的地位非常重要,事实上,我们不可能在使用 Python 的时候不用 None。因此,当大家发现 None 可以被赋值时,不免会感到惊恐(正如提问者一样)。同时,根据 Python 的名称查找机制,查找 None 的速度是比较慢的,因为至少要查找两个字典(查找内置变量字典前,至少会先查找全局变量字典)。

我们最终认为,把 None 作为关键字也没什么问题(实际代码中,不会有人真的给它赋值),而且可以让一些代码运行得稍微快一点,并避免可能存在的错误代码。这个改动的唯一影响就是,需要修改一下解析器和官方文档——因此,我们也没什么好犹豫的。

True 和 False 的情况有所不同。Python 中一开始并没有这两个内置名称,很多人会在自己的代码中定义对应的常量,比如 true/false,True/False 或 TRUE/FALSE。我不记得哪种方式更受欢迎了,但当我们引入 True 和 False 的时候,显然不想影响大家之前所写的代码。(否则会使很多包无法兼容新版本)

因此,我们其实不得不把 True 和 False 作为内置常量,而不是关键字引入。随着时间的推移,自己定义 True 和 False 的代码越来越少,最终,随着 Python 3 的发布,我们终于有机会对语言做一次清理,就把 True 和 False 也设为关键字了,正如 None 一样。


6. 总结

如大家所见,如果了解当时的情况,这整个发展过程是完全符合逻辑的。:-) 抱歉,对这个问题的回复有点长了,我希望对大家有所启发。

更新:我好像忘记回答 None/True/False 到底是字面量还是关键字了。我的答案是,它们既是字面量也是关键字。解析器是把它们当做关键字的,而在一般代码中,它们是作为常量使用的,因而也是字面量。有些人可能会觉得,类似于 {‘foo’: 42} 的表达式也是字面量,个人认为,可能不应该叫字面量,否则 { ‘foo’: x+1} 是不是字面量呢?在文档中,它们其实都被称为“表达(displays)”。"


发表评论

评论列表,共 0 条评论