起步

通过内建方法 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 type’s 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

https://mp.weixin.qq.com/s/VcT4gsS2YuYvRmlHlja34A

一文读懂架构师都不知道的isinstance检查机制相关推荐

  1. mysql 默认事务隔离级别_一文读懂MySQL的事务隔离级别及MVCC机制

    回顾前文: <一文学会MySQL的explain工具> <一文读懂MySQL的索引结构及查询优化> (同时再次强调,这几篇关于MySQL的探究都是基于5.7版本,相关总结与结论 ...

  2. 一文读懂IT行业都有哪些职位

    如今,IT行业正在蓬勃发展,许多朋友都想转向IT行业,因此,最近很多人问我:从事IT行业需要哪些职业技能?IT行业一直都是一个较热门的一个行业,因为它的薪资高,发展好,不像很多职业可能一辈子都没有太多 ...

  3. 阿里专家马飞翔:一文读懂架构整洁之道

    淘系技术 马飞翔(泽畔) 读完需要 9 分钟 速读仅需 3 分钟 相信大家都非常清楚,如何编写可读性强的代码是一个合格程序员的必修课.我在之前的文章<谈谈什么是好的代码>中谈了一些自己对整 ...

  4. 一文读懂架构整洁之道(附知识脉络图)

    程序的世界飞速发展,今天所掌握的技能可能明年就过时了,但有一些东西是历久弥新,永远不变的,掌握了这些,在程序的海洋里就不会迷路,架构思想就是这样一种东西. 本文是<架构整洁之道>的读书笔记 ...

  5. 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

    1.引言 我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ.微信.淘宝.那么,一个大型互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂? ...

  6. 一文读懂数据中台技术架构

    一文读懂数据中台技术架构 https://www.toutiao.com/i6836923386560512516/?tt_from=weixin&utm_campaign=client_sh ...

  7. 一文读懂电商产品架构

    目录 一文读懂电商产品架构 电商平台架构的组成部分 前端展示 后台管理 数据存储

  8. 一文读懂HttpServletRequest

    点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 精彩内容 java实战练习项目教程 全网最全电子图书分享 你所需要的大数据视频 ...

  9. 从根上理解高性能、高并发(七):深入操作系统,一文读懂进程、线程、协程

    本文引用了"一文读懂什么是进程.线程.协程"一文的主要内容,感谢原作者的无私分享. 1.系列文章引言 1.1 文章目的 作为即时通讯技术的开发者来说,高性能.高并发相关的技术概念早 ...

最新文章

  1. AES和TKIP的区别
  2. 背包问题教程-01背包,完全背包,多重背包,混合背包 收藏
  3. VS2017中删项目属性中连接器删除继承的值
  4. C++ vector的初始化、添加、遍历、插入、删除、查找、排序、释放操作
  5. 【python基础】——数据类型(列表、字典、集合)
  6. [爬虫-python]爬取京东100页的图书(机器学习)的信息(价格,打折后价格,书名,作者,好评数,差评数,总评数)
  7. curl返回常见错误码
  8. 201542010208、201571030331软件工程结对项目
  9. Codejock Suite Pro 19.3.0 Carck版本
  10. LPDDR4协议规范之 (一)地址映射和容量计算
  11. 中科院阿里云发布11比特云超导量子处理器
  12. 滴滴悬赏百万寻凶,机智网友支付宝钓鱼转账杀害空姐明珠疑凶
  13. CCF优秀博士学位论文奖初评名单出炉!清华入选4人,数量第一
  14. 基于51单片机的电子密码锁设计
  15. 您的APP上架,遇到【安全评估报告】这道门槛了吗?
  16. SpringBooot
  17. 新手怎么做直播卖货?都有哪些卖货成交话术技巧?
  18. php做到聊天发图片,网页聊天框发送表情图片实现方法
  19. excel模拟运算表
  20. Harvard Dataverse 数据批量下载

热门文章

  1. pythondraw解释_科学网—Draw figures with Python - 高琳琳的博文
  2. asp.net gridview 模板列 弹出窗口编辑_Mac桌面多窗口整理工具Moom,使用教程
  3. matlab jpeg 工具包,安装Matlab JPEG Toolbox
  4. java token身份认证_java – 基于Spring Security Token的身份验证
  5. 绕过模拟器检测_用模拟器玩刺激战场体验差?这样设置轻松吃鸡
  6. python turtle画房子详细解释_简述python的turtle绘画命令及解释
  7. 获取Resources文件下图片的精灵格式
  8. android 电平信号状态识别View平局
  9. 【算法学习笔记】16.暴力求解法04 回溯法03 剪枝法 带宽
  10. 通过SiteMapDataSource动态获取SiteMap文件进行权限设置