笔者目前阅读Effective Python的进度,还在PEP8整理表达式那一小节,收集资料时发现if A
和if A is not None
在具体使用中是有很多区别的,于是先用一篇博客来记录它们之间的区别。
博客主体内容,来源于Stack Overflow上面的问答:
以下是测试代码,具体说明请直接看输出内容。
python
# obj_if.py
class CustomWithoutBool(object):
def __init__(self, num):
print(f'CustomWithoutBool C{num} init.')
self._num = num
def __len__(self):
print(f'CustomWithoutBool C{self._num} call __len__.')
return True
class CustomCls(object):
def __init__(self, num):
print(f'CustomCls C{num} init.')
self._num = num
def __bool__(self):
print(f'CustomCls C{self._num} call __bool__')
return self._num % 2 == 0
def __len__(self):
print(f'CustomCls C{self._num} call __len__')
return True
if __name__ == '__main__':
C3 = CustomCls(3)
if C3:
print('I am C3, my num is odd.')
print('\n--')
C4 = CustomCls(4)
if C4:
print('I am C4, my num is even, so u can see me.')
print('\n--')
CW = CustomWithoutBool(3)
if CW:
print('I am CustomWithoutBool object.')
print('\n--')
print('bool(objs): ', bool(C3), bool(C4), bool(CW))
print('\n--')
if C3 is not None:
print('Here I am not None.')
测试结果:
sql
>py -3 obj_if.py
CustomCls C3 init.
CustomCls C3 call __bool__
--
CustomCls C4 init.
CustomCls C4 call __bool__
I am C4, my num is even, so u can see me.
--
CustomWithoutBool C3 init.
CustomWithoutBool C3 call __len__.
I am CustomWithoutBool object.
--
CustomCls C3 call __bool__
CustomCls C4 call __bool__
CustomWithoutBool C3 call __len__.
bool(objs): False True True
--
Here I am not None.
结论:
if A is not None
只是对A进行非None判定,它比较的是两个对象的地址。
而if A
背后做了好几件事情,它首先检测对象A是否有__bool__方法,如果有,则调用__bool__进行判断并返回结果;如果没有__bool__方法,再检测是否有__len__函数,如果有,则执行__len__函数返回结果;如果__bool__和__len__都不存在,则输出为True。
请注意,__bool__存在时即便__bool__判否,__len__也是不会执行的。
完成上述测试当天,笔者洗澡时候,脑子中冒出来一个新的想法:bool和if,最终执行的代码是否是一致的呢?且待我再测测看。
笔者使用自己之前整理的记录,查看两行代码所对应的字节码。(笔者真的很推荐大家用文字将自己的技能整理记录下来。待到用时,真不含糊,它比搜博客更高效。)
python
# byte_code.py
from obj_if import CustomWithoutBool, CustomCls
C3 = CustomCls(3)
if C3:
print('C3')
bool(C3)
scss
# 直接在Python解释器中执行
>>> s = open('byte_code.py').read()
>>> co = compile(s, 'bc.py', 'exec')
>>> import dis
>>> dis.dis(co)
3 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('CustomWithoutBool', 'CustomCls'))
4 IMPORT_NAME 0 (obj_if)
6 IMPORT_FROM 1 (CustomWithoutBool)
8 STORE_NAME 1 (CustomWithoutBool)
10 IMPORT_FROM 2 (CustomCls)
12 STORE_NAME 2 (CustomCls)
14 POP_TOP
6 16 LOAD_NAME 2 (CustomCls)
18 LOAD_CONST 2 (3)
20 CALL_FUNCTION 1
22 STORE_NAME 3 (C3)
7 24 LOAD_NAME 3 (C3)
26 POP_JUMP_IF_FALSE 18 (to 36)
8 28 LOAD_NAME 4 (print)
30 LOAD_CONST 3 ('C3')
32 CALL_FUNCTION 1
34 POP_TOP
11 >> 36 LOAD_NAME 5 (bool)
38 LOAD_NAME 3 (C3)
40 CALL_FUNCTION 1
42 POP_TOP
44 LOAD_CONST 4 (None)
46 RETURN_VALUE
此处看到if C3
和bool(C3)
的字节指令是不一样的,if C3
是POP_JUMP_IF_FALSE
;bool(C3)
是CALL_FUNCTION
,最终调用bool
函数。
笔者试着继续去Python源码中追一追,去找一找字节码的执行流程,一时并不弄得很明白,于是打住。
但在寻找过程中看到以下代码:
rust
// Objects/object.c
int
PyObject_IsTrue(PyObject *v)
{
Py_ssize_t res;
if (v == Py_True)
return 1;
if (v == Py_False)
return 0;
if (v == Py_None)
return 0;
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);
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);
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;
/* if it is negative, it should be either -1 or -2 */
return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);
}
如函数标题,这函数所做事情,是在判断一个对象是否为True。整个判断逻辑,和最上面搜到并测试的内容一致:
- 它首先判断对象是否为3个特殊对象:True、False和None,如果是,则直接返回结果;
- 接着判断对象身上是否有bool、length函数,如果有,则根据函数执行结果返回最终结果;
- 最后,如果没有这些函数,则返回True。
到此处,笔者不继续往下追。
if A
和if A is not None
,是很有些区别的。