长亭百川云 - 文章详情

Python 的布尔值是怎么实现的,你对它的了解有多深呢?

古明地觉的编程教室

46

2024-07-13

楔子

本篇文章来聊一聊布尔值是怎么实现的,在 Python 里面 True 和 False 虽然是关键字,但它们其实也是对象。

下面我们就来详细地解释一下。

布尔类型

先来说一下布尔类型本身,我们知道 bool 继承自 int,所以 True 和 False 也具备整数的特征。

print(bool.__base__)  # <class 'int'>  
print(isinstance(True, int))  # True  
  
# True 和 False 可以当成 1 和 0 来用  
print(True * 2)  # 2  
print(3 // 3 == True)  # True  
print(sum([True, 1, 2]))  # 4  
print(False + 1)  # 1

bool 在底层对应 PyBool_Type,因此我们可以肯定地讲,PyBool_Type 的 tp_base 字段的值一定是 &PyLong_Type。

// Objects/boolobject.c  
PyTypeObject PyBool_Type = {  
    PyVarObject_HEAD_INIT(&PyType_Type, 0)  
    "bool",  
    offsetof(struct _longobject, long_value.ob_digit),  /* tp_basicsize */  
    sizeof(digit),                              /* tp_itemsize */  
    bool_dealloc,                               /* tp_dealloc */  
    0,                                          /* tp_vectorcall_offset */  
    0,                                          /* tp_getattr */  
    0,                                          /* tp_setattr */  
    0,                                          /* tp_as_async */  
    bool_repr,                                  /* tp_repr */  
    &bool_as_number,                            /* tp_as_number */  
    // ...  
    &PyLong_Type,                               /* tp_base */  
    // ...  
};

bool 继承 int,所以它也实现了 tp_as_number。

布尔值的底层结构

然后是 True 和 False,既然 bool 继承 int,那么布尔值和整数的底层结构是一样的。

// Objects/boolobject.c  
// PyLongObject 是 struct _longobject 的类型别名  
struct _longobject _Py_FalseStruct = {  
    PyObject_HEAD_INIT(&PyBool_Type)  
    { .lv_tag = _PyLong_FALSE_TAG,  
        { 0 }  
    }  
};  
  
struct _longobject _Py_TrueStruct = {  
    PyObject_HEAD_INIT(&PyBool_Type)  
    { .lv_tag = _PyLong_TRUE_TAG,  
        { 1 }  
    }  
};

我们看到布尔值在底层是静态定义好的 PyLongObject 结构体实例,ob_digit 分别为 [0] 和 [1],所以 False 和 True 完全可以当成 0 和 1 来用。

当然啦,由于变量都是 PyObject *,所以这两个结构体实例一般不直接用,而是用底层提供的两个宏。

// Include/boolobject.h  
/* Use these macros */  
#define Py_False _PyObject_CAST(&_Py_FalseStruct)  
#define Py_True _PyObject_CAST(&_Py_TrueStruct)

当返回 Python 的 True 和 False 时,底层会返回 Py_True 和 Py_False,也就是转成 PyObject * 之后再返回。为此解释器还提供了两个宏。

// Include/boolobject.h  
/* Macros for returning Py_True or Py_False, respectively */  
#define Py_RETURN_TRUE return Py_True  
#define Py_RETURN_FALSE return Py_False

当然这些应该比较简单了。

布尔值的创建

创建布尔值有两种方式,一种是基于 C 整数创建,另一种是将 Python 对象转成布尔值。

基于 C 整数创建,会通过 PyBool_FromLong 函数,显然它是布尔对象的特定类型 API。

// Objects/boolobject.c  
// 基于 C 整数创建,对于 C 来说,整数 0 为假,非 0 为真  
PyObject *PyBool_FromLong(long ok)  
{  
    return ok ? Py_True : Py_False;  
}

这个特定类型 API 一般都是解释器内部使用,或者编写扩展的时候使用。而除了这种方式,我们还可以调用 bool 将对象转成布尔值。

// Objects/boolobject.c  
// 基于 Python 对象创建,比如 bool(obj)  
static PyObject *  
bool_new(PyTypeObject *type, PyObject *args, PyObject *kwds)  
{  
    // 保存接收的参数,先设置为 False  
    PyObject *x = Py_False;  
    long ok;  
    // bool 类型不接收关键字参数  
    if (!_PyArg_NoKeywords("bool", kwds))  
        return NULL;  
    // 最多接收 1 个位置参数,解析出来赋值给 x  
    // 如果不传位置参数,那么 x 就是上面设置的 Py_False  
    if (!PyArg_UnpackTuple(args, "bool", 0, 1, &x))  
        return NULL;  
    // 调用 PyObject_IsTrue 判断 x 是真是假  
    // 如果为真返回 1,否则返回 0  
    ok = PyObject_IsTrue(x);  
    if (ok < 0)  
        return NULL;  
    // 将整数转成布尔值  
    return PyBool_FromLong(ok);  
}

所以核心就在 PyObject_IsTrue 函数里面,看一下它的内部逻辑。

// Objects/object.c  
PyObject_IsTrue(PyObject *v)  
{  
    Py_ssize_t res;  
    // 如果 v 是 True,返回 1  
    if (v == Py_True)  
        return 1;  
    // 如果 v 是 False,返回 0  
    if (v == Py_False)  
        return 0;  
    // 如果 v 是 None,返回 0  
    if (v == Py_None)  
        return 0;  
    // 如果 v 是数值型对象,并且它的类型对象定义了 __bool__,那么调用  
    else if (Py_TYPE(v)->tp_as_number != NULL &&  
             Py_TYPE(v)->tp_as_number->nb_bool != NULL)  
        res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v);  
    // 如果 v 是映射型对象,并且它的类型对象定义了 __len__,那么调用  
    // 说白了就是基于内部键值对的个数进行判断  
    else if (Py_TYPE(v)->tp_as_mapping != NULL &&  
             Py_TYPE(v)->tp_as_mapping->mp_length != NULL)  
        res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v);  
    // 如果 v 是序列型对象,并且它的类型对象定义了 __len__,那么调用  
    // 也就是基于内部的元素个数进行判断  
    else if (Py_TYPE(v)->tp_as_sequence != NULL &&  
             Py_TYPE(v)->tp_as_sequence->sq_length != NULL)  
        res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v);  
    // 否则默认为真,比如我们自定义类的实例  
    else  
        return 1;  
    // 如果 res 大于 0,返回 1,否则返回 0  
    return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);  
}

我们用 Python 代码演示一下。

# 整数实现了 __bool__,所以 bool(1) 会调用 int.__bool__(1)  
print(bool(1))  # True  
  
# 字符串实现了 __len__,所以 bool("abc") 会调用 str.__len__("abc")  
print(bool("abc"))  # True  
  
class A:  
    pass  
  
# 自定义类没有实现 __bool__、__len__  
# 所以在 PyObject_IsTrue 里面最终会走 else 分支,直接为真  
print(bool(A()))  # True  
# 但如果定义了 __len__,那么是否为真取决于 __len__ 的返回值  
# 并且 __len__ 要定义在类型对象里面,因为 a.__len__() 其实只是语法糖  
# 底层真正执行的是 A.__len__(a),关于这部分细节后续介绍类的时候会细说  
type.__setattr__(A, "__len__", lambda self: 0)  
# 因为 __len__ 返回的是 0,所以为假,注意:__len__ 要返回整数  
print(bool(A()))  # False  
  
# 如果再实现一个 __bool__ 呢?  
type.__setattr__(A, "__bool__", lambda self: True)  
# 我们发现结果又变成了 True,因为 __bool__ 返回的是 True(必须返回布尔值)  
# 并且在源码中,__bool__ 查找的优先级高于 __len__  
print(bool(A()))  # True

现在你是不是对布尔值有一个更深的印象了呢?一个简单的布尔值,居然有这么多可说的。

但是还没结束,我们还要补充一个知识点,先看一段代码。

name = "satori"  
  
if name:  
    pass  
  
if bool(name):  
    pass

这两个 if 判断有啥区别呢?首先 if bool(name) 我们已经分析过了,它会执行 bool_new 函数,将参数解析出来,接着再调用 PyObject_IsTrue,最后得到布尔值。

而对于 if name 来说,它会直接调用 PyObject_IsTrue,后续在分析 if 语句的时候会介绍。所以在工作中,我们使用 if name 这种形式就好。

当然啦,获取布尔值除了 bool(obj) 之外,还可以使用 not not obj。

name = "satori"  
print(bool(name))  # True  
print(not not name)  # True

这两者又有什么区别呢?首先 bool(name) 在 Python 里面是一个调用,会进行参数解析,拿到对象之后调用 PyObject_IsTrue 判断真假,正常执行的话,会返回 1 或 0。然后基于 1 和 0 创建布尔值,为 1 返回 True,为 0 返回 False。

而 not name 会对应一条 UNARY_NOT 字节码,它内部也会调用 PyObject_IsTrue,如果结果为 1 返回 False,为 0 返回 True,正好是相反的。所以 not not name 则相当于在 not name 的基础上再反过来一次,这样就和 bool(name) 的结果是一致的了。

当然在工作中,无论使用哪种都可以,看自己喜好。但为了代码的可读性,显式获取布尔值的时候还是建议使用 bool(name),效率上没太大差别。

小结

以上就是布尔值相关的内容。

  • bool 继承 int,并且布尔值在底层和整数使用同一个结构体,只是 ob_type 不同;

  • 布尔值具备整数的所有特征,可以像整数一样参与各种运算,其中 True 会被解释成 1,False 会被解释成 0;

  • 布尔值有两种,分别是 True 和 False,它们是永恒对象,并且是单例的。判断时应该使用 is,而不是 ==;

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2