本文出自“Python为何”系列,请查看所有文章html
Python 在涉及真值判断
(Truth Value Testing)时,语法很简便。python
好比,在判断某个对象是否不为 None 时,或者判断容器对象是否不为空时,并不须要显示地写出判断条件,只须要在 if 或 while 关键字后面直接写上该对象便可。android
下图以列表为例,if my_list
这个简短的写法能够表达出两层意思:git
若是须要做出相反的判断,即“若是为 None 或为空”,只须要写成if not my_list
便可。github
一般而言,当一个值自己是布尔类型时,写成"if xxx"(若是真),在语义上就很好理解。若是 xxx 自己不是布尔类型时,写成“if xxx”(若是某东西),则在语义上并很差理解。微信
在 C/C++/Java 之类的静态语言
中,一般要先基于 xxx 做一个比较操做,好比“if (xxx == null)”,以此获得一个布尔类型的值的结果,而后再进行真值判断。不然的话,若“if xxx”中有非布尔类型的值,则会报类型错误。cookie
Python 这门动态语言
在这种场景中表现出了一种灵活性,那么,咱们的问题来了:为何 Python 不须要先作一次比较操做,直接就能对任意对象做真值判断呢?session
先来看看文档 中对真值判断的描述:app
简单而言,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
模块来查看下:
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):
能够看出,对于布尔类型的对象(即 Py_True 和 Py_False),代码会进入到快速处理的分支;而对于其它对象,则会用 PyObject_IsTrue() 计算出一个 int 类型的值。
PyObject_IsTrue() 函数在计算过程当中,依次会获取 nb_bool、mp_length 和 sq_length 的值,对应的应该就是 __bool__() 和 __len__() 这两个魔术方法的返回值。
这个过程就是前文中所引用的官方文档的描述,正是咱们想要找的答案!
另外,对于内置的 bool(),它的核心实现逻辑正是上面的 PyObject_IsTrue() 函数,源码以下(boolobject.c):
因此,Python 在对普通对象做真值判断时,并无隐式地调用 bool(),相反它调用了一个独立的函数(PyObject_IsTrue()),而这个函数又被 bool() 所使用。
也就是说,bool() 与 if/while 语句对普通对象的真值判断,事实上是基本相同的处理逻辑。 知道了原理,就会明白if bool(xxx)
这种写法是画蛇添足的了(我曾见到过)。
至此,咱们已经回答了前文中提出的问题。
接下来,有 3 个测试例子,能够做进一步的验证:
你能够暂停而思考下:bool(Test1)
与 bool(Test1())
各是什么结果?而后依次判断剩下的两个类,结果又会是什么?
揭晓答案:
bool(Test1) # True bool(Test2) # True bool(Test3) # True bool(Test1()) # True bool(Test2()) # False bool(Test3()) # True
缘由以下:
除了这 3 个例子,还有一种状况值得验证,那就是对于数字类型,它们是怎么作真值判断的呢?
咱们能够验证一下数字类型是否拥有那两个魔术方法:
hasattr(2020, "__bool__") hasattr(2020, "__len__")
不难验证出,数字拥有的是 __bool__() 魔术方法,并无__len__() 魔术方法,并且全部类型的数字其实被分红了两类:
__bool__()
返回 False:全部表示 0 的数字,例如0
, 0.0
, 0j
, Decimal(0)
, Fraction(0, 1)
__bool__()
返回 True:全部其它非 0 的数字Python 中if xxx
这种简便的写法,虽然是正规的真值判断语法,并它但并不符合常规的语义。在 C/C++/Java 之类的语言中,要么 xxx 自己是布尔类型的值,要么是一种可返回布尔类型值的操做,可是在 Python 中,这个“xxx”居然还能够是任意的 Python 对象!
本文经过对文档、字节码和 CPython 解释器的源码逐步分析,发现了 Python 的真值判断过程并不简单,能够提炼出如下的几个要点:
False
或者有__len__() 方法返回0
,不然布尔操做的结果都是 True。两个魔术方法老是会先计算__bool__()若是你以为本文分析得不错,那你应该会喜欢这些文章:
四、Python 为何没有 main 函数?为何我不推荐写 main 函数?
六、Python 为何不支持 i++ 自增语法,不提供 ++ 操做符?
七、Python 为何只需一条语句“a,b=b,a”,就能直接交换两个变量?
本文属于“Python为何”系列(Python猫出品),该系列主要关注 Python 的语法、设计和发展等话题,以一个个“为何”式的问题为切入点,试着展示 Python 的迷人魅力。全部文章将会归档在 Github 上,项目地址:https://github.com/chinesehuazhou/python-whydo