-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
65c2176
commit 6fcdafc
Showing
2 changed files
with
264 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
## 楔子 | ||
|
||
本篇文章来聊一聊布尔值是怎么实现的,在 Python 里面 True 和 False 虽然是关键字,但它们其实也是对象。 | ||
|
||
下面我们就来详细地解释一下。 | ||
|
||
## 布尔类型 | ||
|
||
先来说一下布尔类型本身,我们知道 bool 继承自 int,所以 True 和 False 也具备整数的特征。 | ||
|
||
~~~Python | ||
print(bool.__base__) # <class 'int'> | ||
print(isinstance(True, bool)) # 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。 | ||
|
||
~~~c | ||
// Objects/boolobject.c | ||
PyTypeObject PyBool_Type = { | ||
PyVarObject_HEAD_INIT(&PyType_Type, 0) | ||
"bool", | ||
sizeof(struct _longobject), | ||
0, | ||
0, /* 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,那么布尔值和整数的底层结构是一样的。 | ||
|
||
~~~C | ||
// Objects/boolobject.c | ||
|
||
// PyLongObject 是 struct _longobject 的类型别名 | ||
struct _longobject _Py_FalseStruct = { | ||
PyVarObject_HEAD_INIT(&PyBool_Type, 0) | ||
{ 0 } | ||
}; | ||
|
||
struct _longobject _Py_TrueStruct = { | ||
PyVarObject_HEAD_INIT(&PyBool_Type, 1) | ||
{ 1 } | ||
}; | ||
~~~ | ||
|
||
我们看到布尔值在底层是静态定义好的 PyLongObject 结构体实例,ob_digit 分别为 [0] 和 [1],所以 False 和 True 完全可以当成 0 和 1 来用。当然啦,由于变量都是 PyObject \*,所以这两个结构体实例一般不直接用,而是用底层提供的两个宏。 | ||
|
||
~~~C | ||
// Include/boolobject.h | ||
|
||
/* Use these macros */ | ||
#define Py_False ((PyObject *) &_Py_FalseStruct) | ||
#define Py_True ((PyObject *) &_Py_TrueStruct) | ||
~~~ | ||
当返回 Python 的 True 和 False 时,底层会返回 Py_True 和 Py_False,也就是转成 PyObject \* 之后再返回。为此解释器还提供了两个宏。 | ||
~~~C | ||
// Include/boolobject.h | ||
#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True | ||
#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False | ||
~~~ | ||
|
||
当然这些应该比较简单了。 | ||
|
||
## 布尔值的创建 | ||
|
||
创建布尔值有两种方式,一种是基于 C 整数创建,另一种是将 Python 对象转成布尔值。 | ||
|
||
基于 C 整数创建,会通过 PyBool_FromLong 函数,显然它是布尔对象的特定类型 API。 | ||
|
||
~~~C | ||
// Objects/boolobject.c | ||
|
||
PyObject *PyBool_FromLong(long ok) | ||
{ | ||
PyObject *result; | ||
|
||
if (ok) | ||
result = Py_True; | ||
else | ||
result = Py_False; | ||
Py_INCREF(result); | ||
return result; | ||
} | ||
~~~ | ||
这个特定类型 API 一般都是解释器内部使用,或者编写扩展的时候使用。而除了这种方式,我们还可以调用 bool 类型,将对象转成布尔值。 | ||
~~~C | ||
// Objects/boolobject.c | ||
// 基于 Python 对象创建,比如 bool(obj) | ||
// 显然会调用 PyBool_Type 的 tp_new,在底层该字段被赋值为 bool_new | ||
tatic 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_Islrue 判断 x 是真是假 | ||
// 如果为真返回 1,否则返回 0 | ||
ok = PyObject_IsTrue(x); | ||
if (ok < 0) | ||
return NULL; | ||
// 将整数转成布尔值 | ||
return PyBool_FromLong(ok); | ||
} | ||
~~~ | ||
|
||
所以核心就在 PyObject_IsTrue 函数里面,看一下它的内部逻辑。 | ||
|
||
~~~C | ||
// Objects/object.c | ||
int | ||
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 (v->ob_type->tp_as_number != NULL && | ||
v->ob_type->tp_as_number->nb_bool != NULL) | ||
res = (*v->ob_type->tp_as_number->nb_bool)(v); | ||
// 如果 v 是映射型对象,并且它的类型对象定义了 __len__,那么调用 | ||
// 说白了就是基于内部键值对的个数进行判断 | ||
else if (v->ob_type->tp_as_mapping != NULL && | ||
v->ob_type->tp_as_mapping->mp_length != NULL) | ||
res = (*v->ob_type->tp_as_mapping->mp_length)(v); | ||
// 如果 v 是序列型对象,并且它的类型对象定义了__len__,那么调用 | ||
// 也就是基于内部的元素个数进行判断 | ||
else if (v->ob_type->tp_as_sequence != NULL && | ||
v->ob_type->tp_as_sequence->sq_length != NULL) | ||
res = (*v->ob_type->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); | ||
} | ||
~~~ | ||
当 PyObject_IsTrue 调用完之后,再基于 PyBool_FromLong 创建布尔值即可,我们用 Python 代码演示一下。 | ||
~~~python | ||
# 不传参数,默认返回 False | ||
print(bool()) # False | ||
# 整数实现了__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 | ||
~~~ | ||
|
||
现在你是不是对布尔值有一个更深的印象了呢?一个简单的布尔值,居然有这么多可说的。 | ||
|
||
但是还没结束,我们还要补充一个知识点,先看一段代码。 | ||
|
||
~~~python | ||
name = "satori" | ||
|
||
if name: | ||
pass | ||
|
||
if bool(name): | ||
pass | ||
~~~ | ||
|
||
这两个 if 判断有啥区别呢?首先 <font color="blue">if bool(name)</font> 我们已经分析过了,它会执行 bool_new 函数,将参数解析出来,接着再调用 PyObject_IsTrue,最后得到布尔值。 | ||
|
||
而对于 <font color="blue">if name</font> 来说,它会直接调用 PyObject_IsTrue,后续在分析 if 语句的时候会介绍。所以在工作中,我们使用 <font color="blue">if name</font> 即可。 | ||
|
||
当然啦,获取布尔值除了 <font color="blue">bool(obj)</font> 之外,还可以使用 <font color="blue">not not obj</font>。 | ||
|
||
~~~Python | ||
name = "satori" | ||
|
||
print(bool(name)) # True | ||
print(not not name) # True | ||
~~~ | ||
|
||
这两者又有什么区别呢?首先 bool(name) 在 Python 里面是一个调用,会进行参数解析,拿到对象之后调用 PyObject_IsTrue 判断真假,正常执行的话,会返回 1 或 0。然后基于 1 和 0 创建布尔值,为 1 返回 True,为 0 返回 False。 | ||
|
||
而 <font color="blue">not name</font> 会对应一条 UNARY_NOT 字节码,它内部也会调用 PyObject_IsTrue,如果结果为 1 返回 False,为 0 返回 True,正好是相反的。所以 <font color="blue">not not name</font> 则相当于在 <font color="blue">not name</font> 的基础上再反过来一次,这样就和 bool(name) 的结果是一致的了。 | ||
|
||
当然在工作中,无论使用哪种都可以,看自己喜好。但为了代码的可读性,显式获取布尔值的时候还是建议使用 bool(name),效率上没太大差别。 | ||
|
||
## 小结 | ||
|
||
以上就是布尔值相关的内容。 | ||
|
||
+ bool 继承 int,并且布尔值在底层和整数使用同一个结构体,只是 ob_type 不同; | ||
+ 布尔值具备整数的所有特征,可以像整数一样参与各种运算,其中 True 会被解释成 1,False 会被解释成 0; | ||
+ 布尔值有两种,分别是 True 和 False,它们是单例的。判断时应该使用 is,而不是 ==; | ||
+ 在 Python 里面如果要创建布尔值,有三种方式:通过 True 和 False 字面量、调用类型对象 bool,使用 not not; | ||
|
||
----- | ||
|
||
| ||
|
||
**欢迎大家关注我的公众号:古明地觉的编程教室。** | ||
|
||
![](./images/qrcode_for_gh.jpg) | ||
|
||
**如果觉得文章对你有所帮助,也可以请作者吃个馒头,Thanks♪(・ω・)ノ。** | ||
|
||
![](./images/supports.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters