Python在真值测试方面有一个简单的语法。比如判断一个对象是否不为None,或者判断一个容器对象是否不为空,不需要显式写判断条件,直接在if或者while关键字后面写对象即可。下图以列表为例。ifmy_list的简写形式可以表达两种意思:如果需要做相反的判断,即“ifitisNoneorempty”,只需要写成ifnotmy_list。判断真值的不同方式一般来说,当一个值本身是布尔类型时,写成“ifxxx”(如果为真)在语义上很容易理解。如果xxx本身不是boolean类型,写成“ifxxx”(ifsomething)在语义上不太好理解。在C/C++/Java等静态语言中,通常需要根据xxx进行比较运算,比如“if(xxx==null)”,得到一个布尔值的结果,然后执行一个真正的价值判断者。否则,如果“ifxxx”中有非布尔值,就会报类型错误。Python这种动态语言在这种情况下显示出一种灵活性。那么,我们的问题来了:为什么Python不需要先做比较运算,就可以直接判断任意对象的真值呢?先来看看文档中关于真值判断的描述:简单来说,Python中的任何对象都可以用if或while或布尔运算(and,or,not),默认认为是true,除非has___bool__()方法返回False或者__len__()方法返回0。对于前面的例子,my_list没有__bool__()方法,但是它有__len__()方法,所以是否为true取决于这个方法的返回值。真值判断的字节码接下来,我们继续追问底线:为什么Python可以支持如此广泛的真值判断?执行ifxxx这样的语句时,它在做什么?对于第一个问题,Python有一个内置的bool()类型,可以将任何对象转换成布尔值。那么,这是否意味着Python在进行真值判断(即转换为ifbool(xxx))时会隐式调用bool()呢?(答案是否定的,下面有分析)对于第二个问题,可以先用dis模块查看:POP_JUMP_IF_FALSE指令对应if语句的那一行,其含义是:如果TOS为假,则置目标的字节码计数器。TOS弹出。如果栈顶元素为false,则跳转到目标位置。这里只有跳转动作的描述,我还是看不出一个普通的对象是如何变成布尔对象的。Python是如何在解释器中实现真值判断的?真值判断源码实现。在微信群友Jo的帮助下找到了CPython的源码(文件:ceval.c,object.c):可以看到对于boolean类型的对象(即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语句对于普通对象判断真假的处理逻辑基本相同。知道了原理,你就会明白,如果bool(xxx)写的多此一举(我以前见过)。至此,我们已经回答了上一篇文章中提出的问题。验证真假判断的过程接下来还有3个测试例子可以进一步验证:大家可以暂停思考:bool(Test1)和bool(Test1())的结果是什么?然后依次判断剩下的两个类,结果会怎样?揭晓答案:bool(Test1)#Truebool(Test2)#Truebool(Test3)#Truebool(Test1())#Truebool(Test2())#Falsebool(Test3())#True原因如下:对象没有实例化,bool()不会调用它的两个魔法方法__bool__()或__len__()类对象实例化后,如果有__bool__()或__len__()魔法方法,那么bool()会先调用__bool__()方法(PS:这个方法要求返回值是bool类型,所以只要有就可以了,不需要用__len__()方法判断真假)做出真值判断?除了这三个例子,还有一个情况值得验证,那就是他们是如何对数字类型进行真值判断的?我们可以验证数字类型是否有这两个魔法方法:hasattr(2020,"__bool__")hasattr(2020,"__len__")不难验证数字有__bool__()魔法方法,没有__len__()魔术方法,而所有类型的数字其实都分为两类:__bool__()返回False:所有代表0的数字,比如0,0.0,0j,Decimal(0),Fraction(0,1)__bool__()returnsTrue:allothernon-zeronumbers中间ifxxx的简单写法是形式上的真值判断文法,但不符合约定俗成的语义。在C/C++/Java这样的语言中,要么xxx本身就是一个布尔值,要么是一个返回布尔值的运算,但是在Python中,这个“xxx”可以是任意的Python对象!本文通过对文档源码、字节码和CPython解释器的一步步分析,发现Python判断真值的过程并不简单,可以提炼出以下关键点:if/while是隐式布尔运算符:除了“判断”真假的功能外,它们还具有从普通对象隐式计算布尔结果的功能。实际操作是由解释器根据“POP_JUMP_IF_FALSE”指令完成的。它的核心逻辑与内置的bool()共享一个底层方法。真值判断过程依赖于两个神奇的方法:除非被判断对象有一个__bool__()方法返回False或者有一个__len__()方法返回0,否则布尔运算的结果为True。这两个魔术方法总是先计算__bool__()。number类型也可以用于真值判断:Numbers有__bool__()魔术方法,但没有__len__()魔术方法。除代表0的数字外,其他数字均为True
