浅谈Python中重载isinstance继承关系的问题


Posted in Python onMay 04, 2018

判断继承关系

通过内建方法 isinstance(object, classinfo) 可以判断一个对象是否是某个类的实例。这个关系可以是直接,间接或抽象。

实例的检查是允许重载的,可见文档customizing-instance-and-subclass-checks 。根据 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__ 没有调用。 这是怎么回事。

没有运行的 __instancecheck__

可见文档写得并不清楚,为了找出问题,我们从 isinstance 源码开始进行跟踪。

[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 ,这种快速的方式匹配成功的话,也不会去检查 __instancecheck__ 。所以文档中的优先检查是否存在 C.__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 直接构造的。从测试代码中得知判断成立,进入 recursive_isinstance。但是这个函数里面我却没找到有关 __instancecheck__ 的代码,recursive_isinstance 的判断逻辑大致是:

def recursive_isinstance(inst, cls):
  return pyType_IsSubtype(inst, cls)

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

是从 __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 。剩下就是跟踪下 _PyObject_LookupSpecial 就可以了:

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

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

取的是 Py_TYPE(self) ,也就是说指定的 metaclass 里面需要定义 __instancecheck__ 。

总结

至此,总结一下要重载 isinstance(x, C) 行为的条件:

  1. x 对象不能是由 C 直接实例化;
  2. C 类指定 metaclass ;
  3. 指定的 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。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python模块restful使用方法实例
Dec 10 Python
简单易懂的python环境安装教程
Jul 13 Python
Python之多线程爬虫抓取网页图片的示例代码
Jan 10 Python
python flask中静态文件的管理方法
Mar 20 Python
python3.5基于TCP实现文件传输
Mar 20 Python
python得到qq句柄,并显示在前台的方法
Oct 14 Python
使用Python进行目录的对比方法
Nov 01 Python
python自动循环定时开关机(非重启)测试
Aug 26 Python
Python和Anaconda和Pycharm安装教程图文详解
Feb 04 Python
Python类中self参数用法详解
Feb 13 Python
python中用Scrapy实现定时爬虫的实例讲解
Jan 18 Python
Python3爬虫RedisDump的安装步骤
Feb 20 Python
对Python 2.7 pandas 中的read_excel详解
May 04 #Python
Python3读取Excel数据存入MySQL的方法
May 04 #Python
详解Django之admin组件的使用和源码剖析
May 04 #Python
Python实现正弦信号的时域波形和频谱图示例【基于matplotlib】
May 04 #Python
使用python3+xlrd解析Excel的实例
May 04 #Python
对python中的xlsxwriter库简单分析
May 04 #Python
使用实现XlsxWriter创建Excel文件并编辑
May 04 #Python
You might like
PHP批量删除、清除UTF-8文件BOM头的代码实例
2014/04/14 PHP
php将html转成wml的WAP标记语言实例
2015/07/08 PHP
一个简单的php MVC留言本实例代码(必看篇)
2016/09/22 PHP
php实现表单提交上传文件功能
2018/05/28 PHP
Laravel框架运行出错提示RuntimeException No application encryption key has been specified.解决方法
2019/04/02 PHP
Javascript写了一个清除“logo1_.exe”的杀毒工具(可扫描目录)
2007/02/09 Javascript
JS获取scrollHeight问题想到的标准问题
2007/05/27 Javascript
js模拟实现Array的sort方法
2007/12/11 Javascript
jquery固定底网站底部菜单效果
2013/08/13 Javascript
Js保留小数点的4种效果实现代码分享
2014/04/12 Javascript
PHP开发者必须掌握的6个关键字
2014/04/14 Javascript
node.js中的fs.readFileSync方法使用说明
2014/12/15 Javascript
javascript实现限制上传文件大小
2015/02/06 Javascript
在JavaScript中操作时间之getMonth()方法的使用
2015/06/10 Javascript
异步安全加载javascript文件的方法
2015/07/21 Javascript
Bootstrap Table使用整理(四)之工具栏
2017/06/09 Javascript
JS原生轮播图的简单实现(推荐)
2017/07/22 Javascript
js实现网页随机验证码
2020/10/19 Javascript
[03:55]2016国际邀请赛中国区预选赛首日TOP10精彩集锦
2016/06/27 DOTA
遗传算法python版
2018/03/19 Python
Python读取视频的两种方法(imageio和cv2)
2018/04/15 Python
python3安装speech语音模块的方法
2018/12/24 Python
Python中的枚举类型示例介绍
2019/01/09 Python
Python实现在Windows平台修改文件属性
2020/03/05 Python
python中numpy.empty()函数实例讲解
2021/02/05 Python
俄罗斯第一家篮球店:StreetBall
2020/07/30 全球购物
若通过ObjectOutputStream向一个文件中多次以追加方式写入object,为什么用ObjectInputStream读取这些object时会产生StreamCorruptedException?
2016/10/17 面试题
麦当劳辞职信范文
2014/01/18 职场文书
给老婆大人的检讨书
2014/02/24 职场文书
军训口号
2014/06/13 职场文书
劳动竞赛口号
2014/06/16 职场文书
学生检讨书怎么写
2014/10/09 职场文书
导游词怎么写
2015/02/04 职场文书
学校党支部公开承诺书
2015/04/30 职场文书
《梅花魂》教学反思
2016/02/18 职场文书
Python网络编程之ZeroMQ知识总结
2021/04/25 Python