浅谈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。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。