Appearance
Python 内核源码解析:非真非假,那么是什么?
背景
上一篇提到 Python 中 if 条件结果值判断出现了非真 (True) 非假 (False) 的判断,具体代码见指令POP_JUMP_IF_FALSE
和POP_JUMP_IF_TRUE
。看到这样的设计,是不是有点困惑,下面就让我来分析一下。
抽象意义上确实只有真或假,但是在 Python 中(其它弱类型语言也是同样的一个原理),True 或者 False 在判断值是否相等时,
Python 中对于值的判断,真或假是两个分类,分别管理了一类型的值。比如 0,空字符串,空列表,空字典等等,这些值都假。像其它长度大于 0 的字符串,数组、字典,其值是真(限 if 判断)。
POP_JUMP_IF_FALSE 和 POP_JUMP_IF_TRUE 指令解析
POP_JUMP_IF_FALSE
回顾一下指令POP_JUMP_IF_FALSE
,如果栈顶元素为假,则跳转到指定的位置,否则继续执行下一条指令。
c
case TARGET(POP_JUMP_IF_FALSE): { // pop TOS and jump if false
PREDICTED(POP_JUMP_IF_FALSE);
PyObject *cond = POP();
int err;
if (Py_IsTrue(cond)) { // 如果条件为真,则不跳转
Py_DECREF(cond);
DISPATCH();
}
if (Py_IsFalse(cond)) { // 如果条件为假,则跳转到指定位置
Py_DECREF(cond);
JUMPTO(oparg);
CHECK_EVAL_BREAKER();
DISPATCH();
}
err = PyObject_IsTrue(cond);
Py_DECREF(cond);
if (err > 0)
;
else if (err == 0) {
JUMPTO(oparg); // 跳转到指定位置,oparg 是指定位置的偏移量
CHECK_EVAL_BREAKER();
}
else
goto error;
DISPATCH();
}
POP_JUMP_IF_TRUE
POP_JUMP_IF_TRUE
指令代码如下,如果栈顶元素为真,则跳转到指定的位置,否则继续执行下一条指令。
c
case TARGET(POP_JUMP_IF_TRUE): { // pop TOS and jump if true
PREDICTED(POP_JUMP_IF_TRUE);
PyObject *cond = POP();
int err;
if (Py_IsFalse(cond)) { // 如果条件为假,则不跳转
Py_DECREF(cond);
DISPATCH();
}
if (Py_IsTrue(cond)) { // 如果条件为真,则跳转到指定位置
Py_DECREF(cond);
JUMPTO(oparg);
CHECK_EVAL_BREAKER();
DISPATCH();
}
err = PyObject_IsTrue(cond);
Py_DECREF(cond);
if (err > 0) {
JUMPTO(oparg); // 如果条件为真,则跳转到指定位置
CHECK_EVAL_BREAKER();
}
else if (err == 0)
;
else
goto error;
DISPATCH();
}
POP_JUMP_IF_FALSE
和POP_JUMP_IF_TRUE
代码基本一致,只是判断条件的顺序不同,POP_JUMP_IF_FALSE
先判断条件是否为真,如果为真则不跳转,如果为假则跳转到指定位置;POP_JUMP_IF_TRUE
先判断条件是否为假,如果为假则不跳转,如果为真则跳转到指定位置。
起到关键作用的是Py_IsTrue
和PyObject_IsTrue
。接下来就分析一下这两块带代码的实现
Py_IsTrue 是宏定义
Py_False
和Py_True
是全局定义的值,它们分别是_Py_FalseStruct
和_Py_TrueStruct
结构的内存地址,程序启动之后它们的内存地址是不变的。Py_Is(x, y)
其实就是比较两个对象的内存地址是否相等,如果相等则返回 1,否则返回 0。
c
/* Use these macros */
#define Py_False ((PyObject *) &_Py_FalseStruct)
#define Py_True ((PyObject *) &_Py_TrueStruct)
// Test if an object is the True singleton, the same as "x is True" in Python.
PyAPI_FUNC(int) Py_IsTrue(PyObject *x);
#define Py_IsTrue(x) Py_Is((x), Py_True)
// Test if an object is the False singleton, the same as "x is False" in Python.
PyAPI_FUNC(int) Py_IsFalse(PyObject *x);
#define Py_IsFalse(x) Py_Is((x), Py_False)
// Test if the 'x' object is the 'y' object, the same as "x is y" in Python.
PyAPI_FUNC(int) Py_Is(PyObject *x, PyObject *y);
#define Py_Is(x, y) ((x) == (y))
/* The objects representing bool values False and True */
struct _longobject _Py_FalseStruct = {
PyVarObject_HEAD_INIT(&PyBool_Type, 0)
{ 0 }
};
struct _longobject _Py_TrueStruct = {
PyVarObject_HEAD_INIT(&PyBool_Type, 1)
{ 1 }
};
PyObject_IsTrue 是一个函数
PyObject_IsTrue
的参数是一个 PyObject 对象,返回值是一个 int 类型的值,1 为真,0 为假,代码如下。
可以看出 v 有可能是Py_True
、Py_False
、Py_None
、数字类型、映射类型、序列类型的长度,长度等于 0 为假,其它情况都是真。
c
int
PyObject_IsTrue(PyObject *v)
{
Py_ssize_t res;
if (v == Py_True) // 如果 v 是 Py_True,则返回 1
return 1;
if (v == Py_False) // 如果 v 是 Py_False,则返回 0
return 0;
if (v == Py_None) // 如果 v 是 Py_None,则返回 0
return 0;
else if (Py_TYPE(v)->tp_as_number != NULL &&
Py_TYPE(v)->tp_as_number->nb_bool != NULL) // 如果 v 是一个数字类型,则调用 nb_bool 函数
res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v);
else if (Py_TYPE(v)->tp_as_mapping != NULL &&
Py_TYPE(v)->tp_as_mapping->mp_length != NULL) // 如果 v 是一个映射类型,则调用 mp_length 函数
res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v);
else if (Py_TYPE(v)->tp_as_sequence != NULL &&
Py_TYPE(v)->tp_as_sequence->sq_length != NULL) // 如果 v 是一个序列类型,则调用 sq_length 函数
res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v);
else
return 1;
/* if it is negative, it should be either -1 or -2 */
return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);
}
全等和不全等,==和!=
全等和不全等对应的指令是IS_OP
,==和!=对应的指令是COMPARE_OP
。IS_OP
调用了Py_Is
,COMPARE_OP
调用了PyObject_RichCompareBool
。实现原理有出入。
python
>>> a = True
>>> a is True
True
>>> a = 1
>>> a is True
False
>>>
用 dis 模块,输出一下is
的字节码,结果如下。
bash
a = True
a is True
1 0 LOAD_CONST 0 (True)
2 STORE_NAME 0 (a)
2 4 LOAD_NAME 0 (a)
6 LOAD_CONST 0 (True)
8 IS_OP 0
10 POP_TOP
12 LOAD_CONST 1 (None)
14 RETURN_VALUE
IS_OP
字节码的实现如下。
c
case TARGET(IS_OP): { // check object identity
PyObject *right = POP(); // 弹出栈顶的对象
PyObject *left = TOP(); // 取出栈顶的对象
int res = Py_Is(left, right) ^ oparg; // 判断两个对象的内存地址是否相等
PyObject *b = res ? Py_True : Py_False; // 如果相等则返回 Py_True,否则返回 Py_False
Py_INCREF(b);
SET_TOP(b); // 将结果压入栈顶
Py_DECREF(left); // 释放 left 和 right 的引用
Py_DECREF(right); // 释放 left 和 right 的引用
PREDICT(POP_JUMP_IF_FALSE); // 预测下一条指令,是否是 POP_JUMP_IF_FALSE
PREDICT(POP_JUMP_IF_TRUE); // 预测下一条指令,是否是 POP_JUMP_IF_TRUE
DISPATCH();
}
==和!=的实现略微复杂
c
/* Perform a rich comparison, raising TypeError when the requested comparison
operator is not supported. */
static PyObject *
do_richcompare(PyThreadState *tstate, PyObject *v, PyObject *w, int op)
{
richcmpfunc f;
PyObject *res;
int checked_reverse_op = 0;
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
(f = Py_TYPE(w)->tp_richcompare) != NULL) { // 如果 w 是 v 的子类,则调用 w 的 tp_richcompare 函数
checked_reverse_op = 1;
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) { // 调用 v 的 tp_richcompare 函数
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) { // 调用 w 的 tp_richcompare 函数
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
/* If neither object implements it, provide a sensible default
for == and !=, but raise an exception for ordering. */
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
case Py_NE:
res = (v != w) ? Py_True : Py_False;
break;
default:
_PyErr_Format(tstate, PyExc_TypeError,
"'%s' not supported between instances of '%.100s' and '%.100s'",
opstrings[op],
Py_TYPE(v)->tp_name,
Py_TYPE(w)->tp_name);
return NULL;
}
Py_INCREF(res);
return res;
}
总结
- Python 中的 True 和 False 是单例模式,即全局只有一个实例。
- if 是对条件结果值的判断,值如果是 False、None、0、空字符串、空列表、空字典、空元组、空集合,则判断为 False,否则为 True。在弱类型语言使用 if 判断时,需要慎重考虑,是直接使用值,还是需要判断强制判断。
- is 和==的区别,is 是判断两个对象的内存地址是否相等,==是判断两个对象的值是否相等。