一文读懂架构师都不知道的isinstance检查机制(代码片段)

woshijiuke woshijiuke     2022-11-14     721

关键词:

起步

通过内建方法 isinstance(object, classinfo) 可以判断一个对象是否是某个类的实例。但你是否想过关于鸭子协议的对象是如何进行判断的呢? 比如 list 类的父类是继 object 类的,但通过 isinstance([], typing.Iterable) 返回的却是真,难道 list 是可迭代的子类?

根据 PEP 3119 的描述中得知实例的检查是允许重载的:

The primary mechanism proposed here is to allow overloading the built-in functions isinstance() and issubclass(). The overloading works as follows: The call isinstance(x, C) first checks whether C.__instancecheck__ exists, and if so, calls C.__instancecheck__(x) instead of its normal implementation.

 

这段话的意思是,当调用 isinstance(x, C) 进行检测时,会优先检查是否存在 C.instancecheck,如果存在则调用 C.instancecheck(x) ,返回的结果便是实例检测的结果,默认的判断方式就没有了。

这种方式有助于我们来检查鸭子类型,我用代码测了一下。

class Sizeable(object):
    def __instancecheck__(cls, instance):
        print("__instancecheck__ call")
        return hasattr(instance, "__len__")
class B(object):
    pass
b = B()
print(isinstance(b, Sizeable)) # output:False

 

只打印了 False,并且 instancecheck 没有调用。 这是怎么回事。可见文档描述并不清楚。打破砂锅问到底的原则我从源码中观察 isinstance 的检测过程。

从源码来看 isinstance 的检测过程

这部分的内容可能比较难,如果读者觉得阅读有难度可以跳过,直接看结论。isinstance 的源码在 abstract.c 文件中:

[abstract.c]
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;
    ....

 

Py_TYPE(inst) == (PyTypeObject *)cls 这是一种快速匹配的方式,等价于 type(inst) is cls ,这种快速的方式仅当 inst = cls() 匹配成功,并不会去优先检查 instancecheck ,所以文档中有误。继续向下看源码:

/* We know what types __instancecheck__ does. */
    if (PyType_CheckExact(cls)) 
        return recursive_isinstance(inst, cls);
    

 

展开宏 PyType_CheckExact :

[object.h]
#define PyType_CheckExact(op) (Py_TYPE(op) == &PyType_Type)

 

也就是说 cls 是由 type 直接构造出来的类,则判断语言成立。除了类声明里指定 metaclass 外基本都是由 type 直接构造的。从测试代码中得知判断成立,进入 recursiveisinstance。但是这个函数里面我却没找到有关 instancecheck 的代码,recursiveisinstance 的判断逻辑大致是:

def recursive_isinstance(inst, cls):
    return pyType_IsSubtype(inst, cls)
def pyType_IsSubtype(a, b):
    for mro in a.__class__.__mro__:
        if mro is b:
            return True
    return False

 

是从 mro 继承顺序来判断的,mro 是一个元组,它表示类的继承顺序,这个元组的中类的顺序也决定了属性查找顺序。回到 PyObject_IsInstance 函数往下看:

if (PyTuple_Check(cls)) 
    ...

 

这是当 instance(x, C) 第二个参数是元组的情况,里面的处理方式是递归调用 PyObject_IsInstance(inst, item) 。继续往下看:

checker = _PyObject_LookupSpecial(cls, &PyId___instancecheck__);
if (checker != NULL) 
    res = PyObject_CallFunctionObjArgs(checker, inst, NULL);
    ok = PyObject_IsTrue(res);
    return ok;

 

显然,这边才是获得 instancecheck 的地方,为了让检查流程走到这里,定义的类要指明 metaclass 。剩下就是跟踪下 PyObjectLookupSpecial 就可以了:

[typeobject.c]
PyObject *
_PyObject_LookupSpecial(PyObject *self, _Py_Identifier *attrid)

    PyObject *res;
    res = _PyType_LookupId(Py_TYPE(self), attrid);
    // 有回调的话处理回调
    // ...
    return res;

 

取的是 PyTYPE(self) ,也就是说指定的 metaclass 里面需要定义 instancecheck ,获得该属性后,通过 PyObjectCallFunctionObjArgs 调用,调用的内容才是用户自定义的重载方法。

检查机制总结

至此,isinstance 的检测过程基本清晰了,为了便于理解,也得益于python很强的自解释能力,我用python代码来简化 isinstance 的过程:

def _isinstance(x, C):
    # 快速匹配
    if type(x) is C:
        return True
    # 如果是由元类 type 直接构造的类
    if type(C) is type:
        return C in x.__class__.__mro__
    # 如果第二个参数是元组, 则递归调用
    if type(C) is tuple:
        for item in C:
            r = _isinstance(x, item)
            if r:
                return r
    # 用户自定义检测规则
    if hasattr(C, "__instancecheck__"):
        return C.__instancecheck__(x)
    # 默认行为
    return C in x.__class__.__mro__

 

判断的过程中有5个步骤,而用户自定义的 instancecheck 则比较靠后,这个检测过程主要还是以默认的行为来进行的,用户行为并不优先。

重载 isinstance(x, C)

因此,要想重载 isinstance(x, C) ,让用户能自定义判断结果,就需要满足以下条件:

x 对象不能是由 C 直接实例化; 

C 类指定 metaclass ; 

指定的 metaclass 类中定义了 instancecheck 。 

满足这些条件后,比如对鸭子协议如何判断就比较清楚了:

class MetaSizeable(type):
    def __instancecheck__(cls, instance):
        print("__instancecheck__ call")
        return hasattr(instance, "__len__")
class Sizeable(metaclass=MetaSizeable):
    pass
class B(object):
    pass
b = B()
print(isinstance(b, Sizeable))  # output: False
print(isinstance([], Sizeable)) # output: True

 

本次测试环境 Python3.6.0


 

看完以上的内容,相信你对于Python的了解又加深了一层。作为一名Python爱好者,如果你在学习中遇到了困惑需要交流,可以来我们的网站(http://www.magedu.com/)获取帮助,了解行业评价最高的Linux课程可以拨打电话:18519746220。

为什么cto技术总监架构师都不写代码,还这么牛?

常常会被问到这样的问题:CTO、技术总监、架构师很少写具体代码,为什么还很牛逼的样子,拿这么高工资?其实,这个问题本身就错了。就好比问:导演、制片人为什么不懂演戏,还能指导演员,... 查看详情

一文读懂微服务架构的重构策略

你很有可能正在处理大型复杂的单体应用程序,每天开发和部署应用程序的经历都很缓慢而且很痛苦。微服务看起来非常适合你的应用程序,但它也更像是一项遥不可及的必杀技。如何才能走上微服务架构的道路?下面将介绍一... 查看详情

一文读懂devops的本质及行业现状与趋势

一文读懂DevOps的本质及行业现状与趋势导读:GIAC大会期间,来自杰蛙(JFrog)的技术专家刘永强分享了《数据驱动DevOps落地实践》,就此机会,前青云研发总监杨锦涛代表高可用架构对刘永强老师进行了访谈。让我们大家一起看... 查看详情

一文读懂springboot微服务架构和大数据治理之间的故事

微服务架构微服务的诞生并非偶然,它是在互联网高速发展,技术日新月异的变化以及传统架构无法适应快速变化等多重因素的推动下诞生的产物。互联网时代的产品通常有两类特点:需求变化快和用户群体庞大,在这种情况下... 查看详情

为什么cto技术总监架构师都不写代码,还这么牛逼?

常常会被问到这样的问题:CTO、技术总监、架构师很少写具体代码,为什么还很牛逼的样子,拿这么高工资?其实,这个问题本身就错了。就好比问:导演、制片人为什么不懂演戏,还能指导演员,... 查看详情

一文读懂springbean的生命周期(代码片段)

欢迎大家关注我的微信公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。一、前言今天我们来说一说SpringBean的生命周期,小伙伴们应该在面试中经... 查看详情

一文读懂springbean的生命周期(代码片段)

欢迎大家关注我的微信公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。一、前言今天我们来说一说SpringBean的生命周期,小伙伴们应该在面试中经... 查看详情

一文读懂高性能网络编程中的i/o模型

...式已经无能为力。本文(和下篇《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》)旨在为大家提供有用的高性能网络编程的I/O模型概览以及网络服务进程模型的比较,以揭开设计和实现高性能网络架构的神秘面... 查看详情

一文读懂https底层原理

引子先说说我对架构师的理解。从业务能力上,需要的是发现问题和解决问题的能力;从团队建设上,需要的是能培养团队的业务能力;从项目管理上,把控好整个项目和软件产品的全生命周期。我搜索了一... 查看详情

一文读懂mysql-万字长文,肝就完了(代码片段)

本文是加深了解mysql的笔记,比较长,大家可以收藏慢慢看。开胃:先串一下mysql逻辑架构查询语句执行过程:先检查该语句是否有权限,如果没有权限,直接返回错误信息。如果有权限,在MySQL8.0版本... 查看详情

为什么cto技术总监架构师都不写代码,还这么牛?

常常会被问到这样的问题:CTO、技术总监、架构师很少写具体代码,为什么还很牛逼的样子,拿这么高工资?其实,这个问题本身就错了。就好比问:导演、制片人为什么不懂演戏,还能指导演员,... 查看详情

一文读懂链路追踪

背景介绍在微服务横行的时代,服务化思维逐渐成为了程序员的基本思维模式,但是,由于绝大部分项目只是一味地增加服务,并没有对其妥善管理,当接口出现问题时,很难从错综复杂的服务调用网络中找到问题根源,从而错... 查看详情

一文读懂什么是vue

目录VUE是什么 Vue中的核心插件 VueRouterVuexaxioselement-uiVue前端整体架构 VUE是什么Vue(读音/vjuː/,类似于view)是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心... 查看详情

一文读懂rabbitmq消息队列

前言RabbitMQ是一个由Erlang语言开发的AMQP的开源实现。AMQP,即AdvancedMessageQueuingProtocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息... 查看详情

一文读懂rabbitmq消息队列

前言RabbitMQ是一个由Erlang语言开发的AMQP的开源实现。AMQP,即AdvancedMessageQueuingProtocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息... 查看详情

一文读懂kafka的事务机制

1前言大家好,我是明哥!KAFKA作为开源分布式事件流平台,在大数据和微服务领域都有着广泛的应用场景,是实时流处理场景下消息队列事实上的标准。用一句话概括,KAFKA是实时数仓的基石,是事件驱动... 查看详情

阿里p8架构师都是怎么分析软件模型的?

面对一个新项目,如何理解它的模型呢?要先知道项目提供了哪些模型,模型又提供了怎样的能力。若只知道这些,你只是在了解别人设计的结果,这不足以支撑你后期对模型的维护。在一个项目中,常常... 查看详情

一文读懂|linux进程管理之cfs负载均衡

前面的调度学习都是默认在单个CPU上的调度策略。我们知道为了CPU之间减少“干扰”,每个CPU上都有一个任务队列。运行的过程种可能会出现有的CPU“忙的一笔”,有的CPU“闲的蛋疼”,于是便需要负载均衡。将task从负载较重... 查看详情