python|Python 为什么能支持任意的真值判断?


python|Python 为什么能支持任意的真值判断?
本文插图
【python|Python 为什么能支持任意的真值判断?】

作者:豌豆花下猫
来源:Python猫
Python 在涉及真值判断(Truth Value Testing)时 , 语法很简便 。
比如 , 在判断某个对象是否不为 None 时 , 或者判断容器对象是否不为空时 , 并不需要显示地写出判断条件 , 只需要在 if 或 while 关键字后面直接写上该对象即可 。
下图以列表为例 , if my_list 这个简短的写法可以表达出两层意思:
python|Python 为什么能支持任意的真值判断?
本文插图

如果需要作出相反的判断 , 即“如果为 None 或为空” , 只需要写成if not my_list 即可 。
与众不同的真值判断方式 通常而言 , 当一个值本身是布尔类型时 , 写成"if xxx"(如果真) , 在语义上就很好理解 。 如果 xxx 本身不是布尔类型时 , 写成“if xxx”(如果某东西) , 则在语义上并不好理解 。
在 C/C++/Java 之类的静态语言中 , 通常要先基于 xxx 作一个比较操作 , 比如“if (xxx == null)” , 以此得到一个布尔类型的值的结果 , 然后再进行真值判断 。 否则的话 , 若“if xxx”中有非布尔类型的值 , 则会报类型错误 。
Python 这门动态语言在这种场景中表现出了一种灵活性 , 那么 , 我们的问题来了:为什么 Python 不需要先做一次比较操作 , 直接就能对任意对象作真值判断呢?

先来看看文档 中对真值判断的描述:
python|Python 为什么能支持任意的真值判断?
本文插图

简单而言 , Python 的任何对象都可以用在 if 或 while 或布尔操作(and、or、not)中 , 默认情况下认为它是 true , 除非它有__bool__() 方法返回False 或者有__len__() 方法返回0。
对于前面的例子 , my_list 没有__bool__() 方法 , 但是它有__len__() 方法 , 所以它是否为 true , 取决于这个方法的返回值 。
——真值判断的字节码
接着 , 我们继续刨根问底:Python 为什么可以支持如此宽泛的真值判断呢?在执行if xxx这样的语句时 , 它到底在做些什么?
对于第一个问题 , Python 有个内置的 bool() 类型 , 可以将任意对象转化成布尔值 。 那么 , 这是否意味着 Python 在进行真值判断时 , 会隐式地 调用 bool() 呢(即转化成if bool(xxx))?(答案为否 , 下文有分析)
对于第二个问题 , 可以先用dis 模块来查看下:
python|Python 为什么能支持任意的真值判断?
本文插图

POP_JUMP_IF_FALSE指令对应的是 if 语句那行 , 它的含义是:
If TOS is false, sets the bytecode counter to target. TOS is popped.
如果栈顶元素为 false , 则跳转到目标位置 。
这里只有跳转动作的描述 , 仍看不到一个普通对象是如何变成布尔对象的 。

Python 在解释器中到底是如何实现真值判断的呢?
——真值判断的源码实现
在微信群友 Jo 的帮助下 , 我找到了 CPython 的源码(文件:ceval.c、object.c):
python|Python 为什么能支持任意的真值判断?
本文插图

python|Python 为什么能支持任意的真值判断?
本文插图

可以看出 , 对于布尔类型的对象(即 Py_True 和 Py_False) , 代码会进入到快速处理的分支;而对于其它对象 , 则会用 PyObject_IsTrue() 计算出一个 int 类型的值 。


推荐阅读