Python 3.10.4 (main, Mar 31 2022, 03:37:37) [Clang 12.0.0 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from abc import ABCMeta
>>> class A(metaclass=ABCMeta): pass
...
>>> issubclass(int, A)
False
>>> class Meta(type, A): pass
...
>>> issubclass(str, A)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/lutingwang/anaconda3/envs/todd/lib/python3.10/abc.py", line 123, in __subclasscheck__
return _abc_subclasscheck(cls, subclass)
File "/Users/lutingwang/anaconda3/envs/todd/lib/python3.10/abc.py", line 123, in __subclasscheck__
return _abc_subclasscheck(cls, subclass)
TypeError: unbound method type.__subclasses__() needs an argument
正常来说, issubclass
应该返回 False
。但是在定义了 Meta
之后, issubclass
开始报错。两次调用 issubclass
的对象分别是 int
和 str
,是为了绕过 issubclass
的缓存机制。
造成这一现象的原因是, issubclass
需要递归遍历 A
的所有子类,这依赖于的 __subclasses__
方法。通过报错信息不难看出,问题的关键在于 __subclasses__
被错误调用。因此我们怀疑, Meta.__subclasses__
导致了报错。为了验证猜想,我们编写了以下测试代码
>>> Meta.__subclasses__()
Traceback (most recent call last):
File "/Users/lutingwang/Developer/todd/tmp.py", line 13, in <module>
Meta.__subclasses__()
TypeError: unbound method type.__subclasses__() needs an argument
报错信息一致,说明我们的猜想是正确的。因为 Meta
中没有定义 __subclasses__
方法,所以在调用 Meta.__subclasses__
时会自动解析到 type.__subclasses__
。
>>> Meta.__subclasses__
<method '__subclasses__' of 'type' objects>
但是这里的 Meta.__subclasses__
是 Meta
从基类 type
继承的方法,而不是 meta class type
实现的方法。二者的区别在于,继承而来的方法是未绑定的,而 meta class 实现的方法是绑定的。为了解决这一问题,需要重载 Meta.__subclasses__
,使之可以处理未绑定的情况。
class Meta(type, A):
def __subclasses__(self=...) -> List['Meta']:
if self is ...:
return type.__subclasses__(Meta)
return super().__subclasses__()
__isinstance__
底层调用了 __issubclass__
,因此上述分析同样适用。
如果 Meta
进一步被继承,那么在子类中也需要重载
class SubMeta(Meta, A):
def __subclasses__(self=...) -> List['SubMeta']:
if self is ...:
return type.__subclasses__(SubMeta)
return super().__subclasses__()
否则, SubMeta.__subclasses__
将会解析到 Meta.__subclasses__
,然后递归调用 SubMeta.__subclasses__
最终导致递归次数过多,有时表现为段错误。