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


PyObject_IsTrue() 函数在计算过程中 , 依次会获取 nb_bool、mp_length 和 sq_length 的值 , 对应的应该就是 __bool__() 和 __len__() 这两个魔术方法的返回值 。
这个过程就是前文中所引用的官方文档的描述 , 正是我们想要找的答案!
另外 , 对于内置的 bool() , 它的核心实现逻辑正是上面的 PyObject_IsTrue() 函数 , 源码如下(boolobject.c):
python|Python 为什么能支持任意的真值判断?
本文插图

所以 , Python 在对普通对象作真值判断时 , 并没有隐式地调用 bool() , 相反它调用了一个独立的函数(PyObject_IsTrue()) , 而这个函数又被 bool() 所使用 。

也就是说 , bool() 与 if/while 语句对普通对象的真值判断 , 事实上是基本相同的处理逻辑 。 知道了原理 , 就会明白if bool(xxx) 这种写法是多此一举的了(我曾见到过) 。
至此 , 我们已经回答了前文中提出的问题 。
——验证真值判断的过程
接下来 , 有 3 个测试例子 , 可以作进一步的验证:
python|Python 为什么能支持任意的真值判断?
本文插图

你可以暂停而思考下: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__() 方法来判断真假)
——数字类型如何作真值判断?
除了这 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 的真值判断过程并不简单 , 可以提炼出以下的几个要点: