掘金 后端 ( ) • 2024-06-27 22:29

笔者目前阅读Effective Python的进度,还在PEP8整理表达式那一小节,收集资料时发现if Aif A is not None在具体使用中是有很多区别的,于是先用一篇博客来记录它们之间的区别。

博客主体内容,来源于Stack Overflow上面的问答:

https://stackoverflow.com/questions/7816363/if-a-vs-if-a-is-not-none

以下是测试代码,具体说明请直接看输出内容。

# 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.')

测试结果:

>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,最终执行的代码是否是一致的呢?且待我再测测看。

笔者使用自己之前整理的记录,查看两行代码所对应的字节码。(笔者真的很推荐大家用文字将自己的技能整理记录下来。待到用时,真不含糊,它比搜博客更高效。)

# byte_code.py
from obj_if import CustomWithoutBool, CustomCls


C3 = CustomCls(3)
if C3:
    print('C3')


bool(C3)
# 直接在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 C3bool(C3)的字节指令是不一样的,if C3POP_JUMP_IF_FALSEbool(C3)CALL_FUNCTION,最终调用bool函数。

笔者试着继续去Python源码中追一追,去找一找字节码的执行流程,一时并不弄得很明白,于是打住。

但在寻找过程中看到以下代码:

// 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 Aif A is not None,是很有些区别的。