本文出自“Python为什么”系列,请查看全部文章

Python 在涉及真值判断Truth Value Testing)时,语法很简便。

比如,在判断某个对象是否不为 None 时,或者判断容器对象是否不为空时,并不需要显示地写出判断条件,只需要在 if 或 while 关键字后面直接写上该对象即可。

下图以列表为例,if my_list 这个简短的写法可以表达出两层意思:

如果需要作出相反的判断,即“如果为 None 或为空”,只需要写成if not my_list 即可。

与众不同的真值判断方式

通常而言,当一个值本身是布尔类型时,写成"if xxx"(如果真),在语义上就很好理解。如果 xxx 本身不是布尔类型时,写成“if xxx”(如果某东西),则在语义上并不好理解。

在 C/C++/Java 之类的静态语言中,通常要先基于 xxx 作一个比较操作,比如“if (xxx == null)”,以此得到一个布尔类型的值的结果,然后再进行真值判断。否则的话,若“if xxx”中有非布尔类型的值,则会报类型错误。

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 模块来查看下:

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

原因如下:

  • 类对象没被实例化时,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 的真值判断过程并不简单,可以提炼出以下的几个要点:

  • if/while 是隐性的布尔操作符: 它们除了有“判断”真假的作用,还具有隐式地将普通对象计算出布尔结果的功能。实际的操作是解释器根据“POP_JUMP_IF_FALSE”指令来完成的,其核心逻辑跟内置的 bool() 是共用了一个底层方法
  • 真值判断过程依赖两个魔术方法: 除非被判断对象有__bool__() 方法返回False 或者有__len__() 方法返回0 ,否则布尔操作的结果都是 True。两个魔术方法总是会先计算__bool__()
  • 数字类型也可做真值判断: 数字有__bool__() 魔术方法,但没有__len__() 魔术方法,除了表示 0 的数字为 False,其它数字都为 True

如果你觉得本文分析得不错,那你应该会喜欢这些文章:

1、Python为什么使用缩进来划分代码块?

2、Python 的缩进是不是反人类的设计?

3、Python 为什么不用分号作语句终止符?

4、Python 为什么没有 main 函数?为什么我不推荐写 main 函数?

5、Python 为什么推荐蛇形命名法?

6、Python 为什么不支持 i++ 自增语法,不提供 ++ 操作符?

7、Python 为什么只需一条语句“a,b=b,a”,就能直接交换两个变量?

8、Python 为什么用 # 号作注释符?

9、Python 为什么要有 pass 语句?

10、Python 为什么会有个奇怪的“...”对象?

本文属于“Python为什么”系列(Python猫出品),该系列主要关注 Python 的语法、设计和发展等话题,以一个个“为什么”式的问题为切入点,试着展现 Python 的迷人魅力。所有文章将会归档在 Github 上,项目地址:https://github.com/chinesehuazhou/python-whydo

Python 为什么能支持任意的真值判断?的更多相关文章

  1. Python基础(一):程序输入输出、判断合法用户、编写判断成绩的程序

    一.程序输入输出 目标: 编写login.py脚本,实现以下目标: 提示用户输入用户名 将用户名保存在变量中 在屏幕上显示欢迎用户的信息 方案: 编写程序时,很多情况下都需要程序与用户交互.在pyth ...

  2. Python 为什么不支持 switch 语句?

    本文出自"Python为什么"系列,请查看全部文章 在这篇文章里,我们会聊一聊为什么 Python 决定不支持 switch 语句. 为什么想要聊这个话题呢? 主要是因为 swit ...

  3. 【OpenGL(SharpGL)】支持任意相机可平移缩放的轨迹球实现

    [OpenGL(SharpGL)]支持任意相机可平移缩放的轨迹球 (本文PDF版在这里.) 在3D程序中,轨迹球(ArcBall)可以让你只用鼠标来控制模型(旋转),便于观察.在这里(http://w ...

  4. Devrama Slider - 支持任意 HTML 的内容滑块

    Devrama Slider 是一个图片滑块,支持很多特色功能.除了支持图片滑动,其它的 HTML 内容也支持.主要特色:响应式.图片预加载.图片延迟加载.进度条.自定义导航栏和控制按钮等等. 在线演 ...

  5. 【原创】C++链表如何像Python List一样支持多种数据类型

    用过Python的码友都知道,Python中List支持多种数据类型,如下面代码所示链表li内的数据类型可以是整数,同时也可以是字符串,当然也可以是其他数据类型. 1: >>> li ...

  6. 4位或者5位led数码显示,485通信modbus,支持任意小数点写入,工业标准设置,可和plc,dcs,组态完美对接,支持定制修改

    MRD-5030具有4位8段数码管,支持通过工业标注协议Modbus(Modbus-RTU)控制显示,支持任意小数点的显示.数据以半双工方式通信.电源端口和通信端口都具有防浪涌,防雷600W保护,能够 ...

  7. 如何在MQ中实现支持任意延迟的消息?

    什么是定时消息和延迟消息? 定时消息:Producer 将消息发送到 MQ 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消 ...

  8. 阿里云 rds python sdk不支持python3处理

    阿里云文档中心的python版本aliyun-python-sdk-rds不支持python3处理 问题:默认情况下文档中心的python版本只支持python2,不兼容python3版本 需要稍微修 ...

  9. Python 为什么不支持 i++ 自增语法,不提供 ++ 操作符?

    在 C/C++/Java 等等语言中,整型变量的自增或自减操作是标配,它们又可分为前缀操作(++i 和 --i)与后缀操作(i++ 和 i--),彼此存在着一些细微差别,各有不同的用途. 这些语言的使 ...

随机推荐

  1. unity-疑难杂症(一)

    1.使用odin插件序列化,当出现预制体有引用类型的关联, 拖到scene就没关联时,可右键预制体--Reimport解决. 2.类似问题1,脚本组件关联AudioMixer,拖到scene没有关联, ...

  2. Ethical Hacking - NETWORK PENETRATION TESTING(21)

    MITM - Code Injection Inject javascript or HTML code into pages. Code gets executed on target machin ...

  3. Ethical Hacking - NETWORK PENETRATION TESTING(8)

    WEP Cracking Basic case Run airdump-ng to log all traffic from the target network. airodump-ng --cha ...

  4. Java Properties集合基础解析

    Java Properties集合基础解析 本期学习的properties集合是项目中经常用到的操作 什么是Properties集合? java.util.Properties集合继承于Hashtab ...

  5. 数据结构 | 30行代码,手把手带你实现Trie树

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法和数据结构专题的第28篇文章,我们一起来聊聊一个经典的字符串处理数据结构--Trie. 在之前的4篇文章当中我们介绍了关于博弈论的 ...

  6. docker-compose安装zabbix

    在网上的很多帖子,我亲自试过,多数不行,启动后zabbix_server是退出状态,所以觉得自己亲自写一篇帖子,以作记录 1.安装docker和docker-compose yum install - ...

  7. CCNA - Part12 - 路由协议 (1) - 静态路由,动态路由 RIP

    路由器 在之前关于路由器的介绍中,我们知道它是网络互联的核心设备,用于连接不同的网络,在网络之间转发 IP 数据报.对于路由器来说,路由表是其内部最为重要的构成组件.当路由器需要转发数据时,就会按照路 ...

  8. Java8线程池ThreadPoolExecutor底层原理及其源码解析

    小侃一下 日常开发中, 或许不会直接new线程或线程池, 但这些线程相关的基础或思想是非常重要的, 参考林迪效应; 就算没有直接用到, 可能间接也用到了类似的思想或原理, 例如tomcat, jett ...

  9. Android TV 键盘样式开发

    布局: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=& ...

  10. express中post请求模块

    body-parser模块主要解析post接口请求 1.npm install  body-parser  -S 2.server.js中引用 const bodyParser=require('bo ...