Python 3 isinstance() 函数源码解读

yufei       5 年, 9 月 前       1644

isinstance(obj,class_or_tuple) 函数返回一个对象 obj 是否是某个类的实例或者是某个类的子类

参数 class_or_tuple 是可以是多个类组成的元素,例如 isinstance(x,)A,B,...))。如果是一个原则,则分别检查,相当于

isinstance(x,A) or isinstance(x,B) or ...

如果传递的 obj 是一个字面量,则用于检查是否是某个类的实例,否则检查是否是某个类的子类

isinstance()

不知道你是否看过了 Python 3+ 一切都是对象 这篇文章,不管怎么样,你一定会对以下的结果好奇

>>> isinstance(3,object)
True
>>> isinstance(3.14, object)
True
>>> isinstance(max,object)
True

也就是说,看起来,所有的对象都是 object 的实例

>>> isinstance(3,type)
False
>>> isinstance(3.14, type)
False
>>> isinstance(max,type)
False

但看起来所有的字面量又不是 type 的实例

如果你再输入以下代码

>>> isinstance(object,type)
True
>>> isinstance(int,type)
True
>>> isinstance(float,type)
True

是不是要疯掉,因为字面量的类型又是 type 的实例,其实,这种说法是不正确的,应该说这些字面量的类型是 type 类型的子类

这,到底是怎么回事呢?

所有的好奇,都指向了 isinstance() 是如何实现的

我们来翻翻源码,还是 Python 目录下的文件 bltinmodule.c 文件

Python/bltinmodule.c

static PyObject *
builtin_isinstance_impl(PyObject *module, PyObject *obj,
                        PyObject *class_or_tuple)
{
    int retval;

    retval = PyObject_IsInstance(obj, class_or_tuple);
    if (retval < 0)
        return NULL;
    return PyBool_FromLong(retval);
}

你可能很好奇,为什么多了一个参数 module,这个参数是用来表示该函数属于哪个模块的,比如内建函数属于 builtins

>>> isinstance.__module__
'builtins'

先调用 PyObject_IsInstance 判断类型,然后再调用 PyBool_FromLong 格式化结果

既然 retval 能够和 0 做比较,那么 PyObject_IsInstance 的返回值一定是数值类型

所以,为了方便起见,我们先看 PyBool_FromLong 的实现

PyObject *PyBool_FromLong(long ok)
{
    PyObject *result;

    if (ok)
        result = Py_True;
    else
        result = Py_False;
    Py_INCREF(result);
    return result;
}

也就是根据传入的值,转换为 TrueFalse

PyObject_IsInstance()

好了,那继续回到 PyObject_IsInstance()

int
PyObject_IsInstance(PyObject *inst, PyObject *cls)
{
    _Py_IDENTIFIER(__instancecheck__);
    PyObject *checker;

    /* Quick test for an exact match */
    if (Py_TYPE(inst) == (PyTypeObject *)cls)
        return 1;

    /* We know what type's __instancecheck__ does. */
    if (PyType_CheckExact(cls)) {
        return recursive_isinstance(inst, cls);
    }

    if (PyTuple_Check(cls)) {
        Py_ssize_t i;
        Py_ssize_t n;
        int r = 0;

        if (Py_EnterRecursiveCall(" in __instancecheck__"))
            return -1;
        n = PyTuple_GET_SIZE(cls);
        for (i = 0; i < n; ++i) {
            PyObject *item = PyTuple_GET_ITEM(cls, i);
            r = PyObject_IsInstance(inst, item);
            if (r != 0)
                /* either found it, or got an error */
                break;
        }
        Py_LeaveRecursiveCall();
        return r;
    }

    checker = _PyObject_LookupSpecial(cls, &PyId___instancecheck__);
    if (checker != NULL) {
        PyObject *res;
        int ok = -1;
        if (Py_EnterRecursiveCall(" in __instancecheck__")) {
            Py_DECREF(checker);
            return ok;
        }
        res = PyObject_CallFunctionObjArgs(checker, inst, NULL);
        Py_LeaveRecursiveCall();
        Py_DECREF(checker);
        if (res != NULL) {
            ok = PyObject_IsTrue(res);
            Py_DECREF(res);
        }
        return ok;
    }
    else if (PyErr_Occurred())
        return -1;
    /* Probably never reached anymore. */
    return recursive_isinstance(inst, cls);
}

_Py_IDENTIFIER

_Py_IDENTIFIER(__instancecheck__)

_Py_IDENTIFIER 相关的代码如下

Include/object.h

typedef struct _Py_Identifier {
    struct _Py_Identifier *next;
    const char* string;
    PyObject *object;
} _Py_Identifier;

#define _Py_static_string_init(value) { .next = NULL, .string = value, .object = NULL }
#define _Py_static_string(varname, value)  static _Py_Identifier varname = _Py_static_string_init(value)
#define _Py_IDENTIFIER(varname) _Py_static_string(PyId_##varname, #varname)

他们的作用是管理静态字符串,基本的使用方式如下

假设存在一个函数调用

r = PyObject_CallMethod(o, "foo", "args", ...);

那么使用 _Py_IDENTIFIER 就可以写成下面这样啦

_Py_IDENTIFIER(foo);

...

r = _PyObject_CallMethodId(o, &PyId_foo, "args", ...);

也就是 _Py_IDENTIFIER(foo); 会自动创建一个静态的字符串变量 PyId_foo,可以是块级别的,也可以是文件级别的

C++ 编译器在预处理的时候,会将字符串 foo 预处理成 PyId_foo 并连接到指定结构

当预处理结束,所有的字符串都通过 _PyUnicode_ClearStaticStrings 被释放

所以 _Py_IDENTIFIER(__instancecheck__) 相当于创建了一个名为 PyId__instancecheck__ 的块级的静态变量

从后面的代码也可以看出,的确如此

Py_TYPE

if (Py_TYPE(inst) == (PyTypeObject *)cls)
        return 1;

用于检查 inst 中的 ob_type 的值是否就是 cls,如果是则直接返回 1

Py_TYPE() 的实现也在 object.h

#define Py_TYPE(ob)             (((PyObject*)(ob))->ob_type)

PyType_CheckExact

if (PyType_CheckExact(cls)) {
    return recursive_isinstance(inst, cls);
}

这段代码用于检查 cls 是否就是 type 类型,如果是的话则递归检查 inst 的继承链

什么意思呢? 其实就是除了 type 类型的类型是 type 外,所有用户自定义的类型的 type() 结果都是 type

不信的话,你可以自己测试下下面代码

>>> class Foo:
...     pass
... 
>>> Foo.__class__
<class 'type'>
>>> class Foo(int):
...     pass
... 
>>> type(Foo)
<class 'type'>

recursive_isinstance

recursive_isinstance() 函数的源代码在 Objects/abstract.c

static int
recursive_isinstance(PyObject *inst, PyObject *cls)
{
    PyObject *icls;
    int retval = 0;
    _Py_IDENTIFIER(__class__);

    if (PyType_Check(cls)) {
        retval = PyObject_TypeCheck(inst, (PyTypeObject *)cls);
        if (retval == 0) {
            PyObject *c = _PyObject_GetAttrId(inst, &PyId___class__);
            if (c == NULL) {
                if (PyErr_ExceptionMatches(PyExc_AttributeError))
                    PyErr_Clear();
                else
                    retval = -1;
            }
            else {
                if (c != (PyObject *)(inst->ob_type) &&
                    PyType_Check(c))
                    retval = PyType_IsSubtype(
                        (PyTypeObject *)c,
                        (PyTypeObject *)cls);
                Py_DECREF(c);
            }
        }
    }
    else {
        if (!check_class(cls,
            "isinstance() arg 2 must be a type or tuple of types"))
            return -1;
        icls = _PyObject_GetAttrId(inst, &PyId___class__);
        if (icls == NULL) {
            if (PyErr_ExceptionMatches(PyExc_AttributeError))
                PyErr_Clear();
            else
                retval = -1;
        }
        else {
            retval = abstract_issubclass(icls, cls);
            Py_DECREF(icls);
        }
    }

    return retval;
}

这个函数很简单,以 PyType_Check(cls) 作为分水岭,检查 cls__class__ 属性是否为 <class 'type'>

  1. 如果是的话,使用下面的代码检查 instob_type 是否就是 cls,或者 cls 是否就是 inst 的父类之一
    retval = PyObject_TypeCheck(inst, (PyTypeObject *)cls);
    

其中 PyObject_TypeCheck 的源码如下

    #define PyObject_TypeCheck(ob, tp) \
    (Py_TYPE(ob) == (tp) || PyType_IsSubtype(Py_TYPE(ob), (tp)))

如果是则直接返回检查的结果 retval

如果不是,且有没有定义 __class__ 属性则抛出异常

如果不是,但定义了 __class__ 属性,那么就检查 __class__ 的类是否是 clscls 的子类

  1. 如果不是的话,则直接检查 __class__ 属性,再根据是否存在 __class__ 属性来检查

recursive_isinstance() 函数解析就到此为止吧,源码很简单,一看九洞

### 如果 cls 参数是元组

继续下面的代码

```c if (PyTuple_Check(cls)) { Py_ssize_t i; Py_ssize_t n; int r = 0;

    if (Py_EnterRecursiveCall(" in __instancecheck__"))
        return -1;
    n = PyTuple_GET_SIZE(cls);
    for (i = 0; i < n; ++i) {
        PyObject *item = PyTuple_GET_ITEM(cls, i);
        r = PyObject_IsInstance(inst, item);
        if (r != 0)
            /* either found it, or got an error */
            break;
    }
    Py_LeaveRecursiveCall();
    return r;
}

```

检查参数 class_or_tuple 是否是一个元组,如果是元组则迭代检查元组中某个元素是否就是 inst 的类型或 inst 的父类的类型

最后,检查 inst 是否是 cls 的实例

如果上面的检查都得不到结果,那么只能检查 inst__instancecheck__ 属性了

这个属性也非常有意思

>>> class Foo(int):
...     pass
... 
>>> type(Foo)
<class 'type'>
>>> Foo.__instancecheck__
<built-in method __instancecheck__ of type object at 0x7f9bfe703148>
>>> int.__instancecheck__
<built-in method __instancecheck__ of type object at 0x108a72d70>
>>> float.__instancecheck__
<built-in method __instancecheck__ of type object at 0x108a6f8b0>

下面一系列的代码

>>> float.__instancecheck__(3.14)
True
>>> float.__instancecheck__(3)
False
>>> type.__instancecheck__(3.14)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__instancecheck__' requires a 'type' object but received a 'float'
>>> object.__instancecheck__(3)
True

算了,这章节就到此结束吧,下一章节我们来详细介绍下 __instancecheck__ 这个属性

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.