一文读懂架构师都不知道的isinstance检查机制
起步
通过内建方法 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检查机制相关推荐
- mysql 默认事务隔离级别_一文读懂MySQL的事务隔离级别及MVCC机制
回顾前文: <一文学会MySQL的explain工具> <一文读懂MySQL的索引结构及查询优化> (同时再次强调,这几篇关于MySQL的探究都是基于5.7版本,相关总结与结论 ...
- 一文读懂IT行业都有哪些职位
如今,IT行业正在蓬勃发展,许多朋友都想转向IT行业,因此,最近很多人问我:从事IT行业需要哪些职业技能?IT行业一直都是一个较热门的一个行业,因为它的薪资高,发展好,不像很多职业可能一辈子都没有太多 ...
- 阿里专家马飞翔:一文读懂架构整洁之道
淘系技术 马飞翔(泽畔) 读完需要 9 分钟 速读仅需 3 分钟 相信大家都非常清楚,如何编写可读性强的代码是一个合格程序员的必修课.我在之前的文章<谈谈什么是好的代码>中谈了一些自己对整 ...
- 一文读懂架构整洁之道(附知识脉络图)
程序的世界飞速发展,今天所掌握的技能可能明年就过时了,但有一些东西是历久弥新,永远不变的,掌握了这些,在程序的海洋里就不会迷路,架构思想就是这样一种东西. 本文是<架构整洁之道>的读书笔记 ...
- 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面
1.引言 我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ.微信.淘宝.那么,一个大型互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂? ...
- 一文读懂数据中台技术架构
一文读懂数据中台技术架构 https://www.toutiao.com/i6836923386560512516/?tt_from=weixin&utm_campaign=client_sh ...
- 一文读懂电商产品架构
目录 一文读懂电商产品架构 电商平台架构的组成部分 前端展示 后台管理 数据存储
- 一文读懂HttpServletRequest
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 精彩内容 java实战练习项目教程 全网最全电子图书分享 你所需要的大数据视频 ...
- 从根上理解高性能、高并发(七):深入操作系统,一文读懂进程、线程、协程
本文引用了"一文读懂什么是进程.线程.协程"一文的主要内容,感谢原作者的无私分享. 1.系列文章引言 1.1 文章目的 作为即时通讯技术的开发者来说,高性能.高并发相关的技术概念早 ...
最新文章
- AES和TKIP的区别
- 背包问题教程-01背包,完全背包,多重背包,混合背包 收藏
- VS2017中删项目属性中连接器删除继承的值
- C++ vector的初始化、添加、遍历、插入、删除、查找、排序、释放操作
- 【python基础】——数据类型(列表、字典、集合)
- [爬虫-python]爬取京东100页的图书(机器学习)的信息(价格,打折后价格,书名,作者,好评数,差评数,总评数)
- curl返回常见错误码
- 201542010208、201571030331软件工程结对项目
- Codejock Suite Pro 19.3.0 Carck版本
- LPDDR4协议规范之 (一)地址映射和容量计算
- 中科院阿里云发布11比特云超导量子处理器
- 滴滴悬赏百万寻凶,机智网友支付宝钓鱼转账杀害空姐明珠疑凶
- CCF优秀博士学位论文奖初评名单出炉!清华入选4人,数量第一
- 基于51单片机的电子密码锁设计
- 您的APP上架,遇到【安全评估报告】这道门槛了吗?
- SpringBooot
- 新手怎么做直播卖货?都有哪些卖货成交话术技巧?
- php做到聊天发图片,网页聊天框发送表情图片实现方法
- excel模拟运算表
- Harvard Dataverse 数据批量下载
热门文章
- pythondraw解释_科学网—Draw figures with Python - 高琳琳的博文
- asp.net gridview 模板列 弹出窗口编辑_Mac桌面多窗口整理工具Moom,使用教程
- matlab jpeg 工具包,安装Matlab JPEG Toolbox
- java token身份认证_java – 基于Spring Security Token的身份验证
- 绕过模拟器检测_用模拟器玩刺激战场体验差?这样设置轻松吃鸡
- python turtle画房子详细解释_简述python的turtle绘画命令及解释
- 获取Resources文件下图片的精灵格式
- android 电平信号状态识别View平局
- 【算法学习笔记】16.暴力求解法04 回溯法03 剪枝法 带宽
- 通过SiteMapDataSource动态获取SiteMap文件进行权限设置