浅谈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格式化字符串实例总结
Sep 28 Python
举例简单讲解Python中的数据存储模块shelve的用法
Mar 03 Python
python常用函数详解
Sep 13 Python
老生常谈Python序列化和反序列化
Jun 28 Python
Python 2.x如何设置命令执行的超时时间实例
Oct 19 Python
使用Python横向合并excel文件的实例
Dec 11 Python
python实现文件的分割与合并
Aug 29 Python
python pyqtgraph 保存图片到本地的实例
Mar 14 Python
哈工大自然语言处理工具箱之ltp在windows10下的安装使用教程
May 07 Python
使用python实现下载我们想听的歌曲,速度超快
Jul 09 Python
Python绘图之柱形图绘制详解
Jul 28 Python
Python实现批量自动整理文件
Mar 16 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
DISCUZ 论坛管理员密码忘记的解决方法
2009/05/14 PHP
php 中英文语言转换类代码
2011/08/11 PHP
实用的PHP带公钥加密类分享(每次加密结果都不一样哦)
2014/08/20 PHP
smarty学习笔记之常见代码段用法总结
2016/03/19 PHP
PHPExcel实现表格导出功能示例【带有多个工作sheet】
2018/06/13 PHP
PHP基于openssl实现的非对称加密操作示例
2019/01/11 PHP
js 无提示关闭浏览器页面的代码
2010/03/09 Javascript
使用jQuery操作Cookies的实现代码
2011/10/09 Javascript
js实现文章文字大小字号功能完整实例
2014/11/01 Javascript
js 加密压缩出现bug解决方案
2014/11/25 Javascript
jQuery中extend函数的实现原理详解
2015/02/03 Javascript
angularjs表格ng-table使用备忘录
2016/03/09 Javascript
关于jquery中动态增加select,事件无效的快速解决方法
2016/08/29 Javascript
js创建对象几种方式的优缺点对比
2016/09/28 Javascript
Vue CLI项目 axios模块前后端交互的使用(类似ajax提交)
2019/09/01 Javascript
vue-resource:jsonp请求百度搜索的接口示例
2019/11/09 Javascript
在微信小程序中渲染HTML内容3种解决方案及分析与问题解决
2020/01/12 Javascript
解决Vue @submit 提交后不刷新页面问题
2020/07/18 Javascript
[01:42]辉夜杯战队访谈宣传片—FANTUAN
2015/12/25 DOTA
python 判断自定义对象类型
2009/03/21 Python
利用pyuic5将ui文件转换为py文件的方法
2019/06/19 Python
pycharm通过ssh连接远程服务器教程
2020/02/12 Python
详解pytorch tensor和ndarray转换相关总结
2020/09/03 Python
YSL Beauty加拿大官方商城:圣罗兰美妆加拿大
2017/05/15 全球购物
Servlet如何得到客户端机器的信息
2014/10/17 面试题
小学教师学期末自我评价
2013/09/25 职场文书
教研活动总结
2014/04/28 职场文书
项目建议书怎么写
2014/05/15 职场文书
商务经理岗位职责
2014/07/30 职场文书
2014法院四风问题对照检查材料思想汇报
2014/10/04 职场文书
2015年机关纠风工作总结
2015/05/15 职场文书
幼儿园大班开学寄语(2015秋季)
2015/05/27 职场文书
旅游安全责任协议书
2016/03/22 职场文书
优秀范文:读《红岩》有感3篇
2019/10/14 职场文书
python实现局部图像放大
2021/11/17 Python
JavaScript选择器函数querySelector和querySelectorAll
2021/11/27 Javascript