python的自省机制也是其一大彪悍的特性,对于任何一个对象,我们都可以准确的获取其类型。
print(type(123))
print(type(""))
print(type(str))
print(type(map))
"""
<class 'int'>
<class 'str'>
<class 'type'>
<class 'type'>
"""
但是这种获取方式有一个缺陷,比如说:
class A:
pass
class B(A):
pass
b = B()
print(type(b)) # <class '__main__.B'>
这里B继承自A,但是b是由B这个类实例化出来的,因此如果type(b)的话,打印的就是B这个类。但是我要怎么判断b和A之间的关系呢?
class A:
pass
class B(A):
pass
b = B()
print(isinstance(b, A)) # True
显然可以使用isinstance,如果A是B的父类,那么b不仅是B的实例对象,同时也是A的实例对象。但是这里问题就来了,这个isinstance的检测机制是什么?我们可以再来看一个例子。
from collections.abc import Iterable
s = ""
l = []
d = {}
print(isinstance(s, Iterable))
print(isinstance(l, Iterable))
print(isinstance(d, Iterable))
"""
True
True
True
"""
我们发现字符串对象、列表对象、字典对象都继承自Iterable,因为它们都是可迭代的的。因此这个isinstance比type的范围要更广泛一些,type得到的就是具体实例化该对象类,而isinstance则貌似是要求满足某种特定的要件就可以。我们来看一下源码。
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
return _check_methods(C, "__contains__")
return NotImplemented
我们注意到这些都是一个抽象基类,里面定义了一个__subclasshook__
方法,其实关键就在这里。
from abc import ABCMeta
class A(metaclass=ABCMeta):
@classmethod
def __subclasshook__(cls, C):
# 当我们使用isinstance(obj, A)的时候
# 那么实例化obj所对应类,就会作为参数传递给C
# 我们可以自定义,比如这里
# 如果C有fuck这个属性,那么就认为obj也是A的实例
return hasattr(C, "fuck")
class S:
def fuck(self):
pass
s = S()
print(isinstance(s, A)) # True
# 我们注意到S这个类没有继承任何东西,但是它确是A的实例
# 因为我们自己实现了抽象基类
# 不仅如此,此时的S这个类还是A的子类
print(issubclass(S, A)) # True
以上です,希望各位能明白isinstance的机制。