一个学习记录贴,随便写点,大家见笑了
这几天有人来问我一个问题,Python里1和True是不是完全等价的,因为1 == True结果是True,再加上if 1和if True效果一样,以及我印象中c里面1和true是完全等价的,所以我和ta说是的。但ta反驳我说这俩类型不一样,1 is True的结果是False,这一下给我也问懵了,于是就去研究了一下(开源的好处不就在这了吗),发现还挺有意思,就分享一下(把这当水区了)。
首先从Python的老祖宗c说起,1和true是定义在<stdbool.h>里的:
#define bool _Bool
#define true 1
#define false 0
就是这么简单粗暴,一样,没啥好说的。
小声明:本文引用的Pthon源代码均由Python软件基金会许可证开源
至于Python,先说if 1的问题吧,先引用一段官方文档里的话吧
在执行布尔运算的情况下,或是当表达式被用于流程控制语句时,以下值会被解读为假值: False, None, 所有类型的数字零,以及空字符串和空容器(包括字符串、元组、列表、字典、集合与冻结集合)。 所有其他值都会被解读为真值。
就很难评。也就是说if 2之类的也和if True等价。(原理后面解释)
再说说1 == True吧。在Python中,布尔型是整数型的子类(这个后面解释),True 被定义为 1,而 False 被定义为 0。在进行比较时,True被自动转换为其对应的整数值 1(具体也得后面讲),1 == 1显然是True啦。而两者的标识号(就是id)不同,自然1 is True返回False了。
详细扒一扒吧
Part 1
我去翻了一翻Python的源代码(https://github.com/python/cpython)
1的定义应该在Objects/longobject.c还有Include/cpython /longintrepr.h中吧,我没细看,不重要。
再说True吧。cPython中实现布尔型的实现层在Objects/longobject.c,接口层在Include/boolobject.h
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 }
}
};
由这一坨可以知道True和False分别被定义为全局静态对象_Py_TrueStruct和_Py_FalseStruct,使用_longobject结构体,继承自长整型,实际分别储存着整数值1和0,而类型指针指向PyBool_Type。
这个PyBool_Type的定义长这个样子
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 */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
bool_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&PyLong_Type, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
bool_new, /* tp_new */
.tp_vectorcall = bool_vectorcall,
};
其中规定了类型名称为"bool",而&PyLong_Type,这一行说明了ta的基类指向int,这就是为什么说布尔型是整型的子类了。
Part 2
接着来分析前面那句文档中的话,就是关于从其他对象创建布尔值的,在这里是由
PyObject *PyBool_FromLong(long ok)
{
return ok ? Py_True : Py_False;
}
实现的。可以看出这里的返回值是根据ok是否为零决定的。
非零->True,零->False,也就是上面我引用的文档里的话。
Part 3
最后再说说True和1的转换问题吧。
可以看到代码中定义了很多布尔型的运算,我以&举例,代码长这样:
static PyObject *
bool_and(PyObject *a, PyObject *b)
{
if (!PyBool_Check(a) || !PyBool_Check(b))
return PyLong_Type.tp_as_number->nb_and(a, b);
return PyBool_FromLong((a == Py_True) & (b == Py_True));
}
其中用的类型检查宏在这
#define PyBool_Check(x) (Py_IS_TYPE(x, &PyBool_Type))
以及布尔型作为整数时的运行模式在这
static PyNumberMethods bool_as_number = {
0, /* nb_add */
0, /* nb_subtract */
0, /* nb_multiply */
0, /* nb_remainder */
0, /* nb_divmod */
0, /* nb_power */
0, /* nb_negative */
0, /* nb_positive */
0, /* nb_absolute */
0, /* nb_bool */
bool_invert, /* nb_invert */
0, /* nb_lshift */
0, /* nb_rshift */
bool_and, /* nb_and */
bool_xor, /* nb_xor */
bool_or, /* nb_or */
0, /* nb_int */
0, /* nb_reserved */
0, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
0, /* nb_inplace_multiply */
0, /* nb_inplace_remainder */
0, /* nb_inplace_power */
0, /* nb_inplace_lshift */
0, /* nb_inplace_rshift */
0, /* nb_inplace_and */
0, /* nb_inplace_xor */
0, /* nb_inplace_or */
0, /* nb_floor_divide */
0, /* nb_true_divide */
0, /* nb_inplace_floor_divide */
0, /* nb_inplace_true_divide */
0, /* nb_index */
};
注意:bool_and 实现的是按位与运算符 & 的行为,而不是逻辑关键字and。逻辑and是解释器在字节码层面实现的短路运算,不会调用这个函数。
总结
就以一段代码来解释Python中True 和1的区别和联系吧
assert True == 1 #值相等
assert True is not 1 #但对象不同
assert isinstance(True, int) #是整型的子类
assert True + True == 2 #支持整数运算
结尾
写的太水了,本来打算放水区的,但我觉得这个问题还挺有意义的,也欢迎看到大家的讨论与分析吧