浅谈Python中重载isinstance继承关系的问题
判断继承关系
通过内建方法isinstance(object,classinfo)可以判断一个对象是否是某个类的实例。这个关系可以是直接,间接或抽象。
实例的检查是允许重载的,可见文档customizing-instance-and-subclass-checks。根据PEP3119的描述:
Theprimarymechanismproposedhereistoallowoverloadingthebuilt-infunctionsisinstance()andissubclass().Theoverloadingworksasfollows:Thecallisinstance(x,C)firstcheckswhetherC.__instancecheck__exists,andifso,callsC.__instancecheck__(x)insteadofitsnormalimplementation.
这段话的意思是,当调用isinstance(x,C)进行检测时,会优先检查是否存在C.__instancecheck__,如果存在则调用C.__instancecheck__(x),返回的结果便是实例检测的结果,默认的判断方式就没有了。
这种方式有助于我们来检查鸭子类型,我用代码测了一下。
classSizeable(object): def__instancecheck__(cls,instance): print("__instancecheck__call") returnhasattr(instance,"__len__") classB(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; /*Quicktestforanexactmatch*/ if(Py_TYPE(inst)==(PyTypeObject*)cls) return1; .... }
Py_TYPE(inst)==(PyTypeObject*)cls这是一种快速匹配的方式,等价于type(inst)iscls,这种快速的方式匹配成功的话,也不会去检查__instancecheck__。所以文档中的优先检查是否存在C.__instancecheck__有误。继续向下看源码:
/*Weknowwhattype's__instancecheck__does.*/ if(PyType_CheckExact(cls)){ returnrecursive_isinstance(inst,cls); }
展开宏PyType_CheckExact:
[object.h] #definePyType_CheckExact(op)(Py_TYPE(op)==&PyType_Type)
也就是说cls是由type直接构造出来的类,则判断语言成立。除了类声明里指定metaclass外基本都是由type直接构造的。从测试代码中得知判断成立,进入recursive_isinstance。但是这个函数里面我却没找到有关__instancecheck__的代码,recursive_isinstance的判断逻辑大致是:
defrecursive_isinstance(inst,cls): returnpyType_IsSubtype(inst,cls) defpyType_IsSubtype(a,b): formroina.__mro__: ifmroisb: returnTrue returnFalse
是从__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); returnok; }
显然,这边才是获得__instancecheck__的地方,为了让检查流程走到这里,定义的类要指明metaclass。剩下就是跟踪下_PyObject_LookupSpecial就可以了:
[typeobject.c] PyObject* _PyObject_LookupSpecial(PyObject*self,_Py_Identifier*attrid) { PyObject*res; res=_PyType_LookupId(Py_TYPE(self),attrid); //有回调的话处理回调 //... returnres; }
取的是Py_TYPE(self),也就是说指定的metaclass里面需要定义__instancecheck__。
总结
至此,总结一下要重载isinstance(x,C)行为的条件:
- x对象不能是由C直接实例化;
- C类指定metaclass;
- 指定的metaclass类中定义了__instancecheck__。
测试代码:
classMetaSizeable(type): def__instancecheck__(cls,instance): print("__instancecheck__call") returnhasattr(instance,"__len__") classSizeable(metaclass=MetaSizeable): pass classB(object): pass b=B() print(isinstance(b,Sizeable))#output:False print(isinstance([],Sizeable))#output:True
文档可能有点老旧了。本次测试的环境是Python3.6。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。