编写的Tombola示例测试脚本用到两个类属性,用它们内省类的继承关
__subclasses__()
这个方法返回类的直接子类列表,不含虚拟子类。
_abc_registry
只有抽象基类有这个数据属性,其值是一个WeakSet对象,即抽象类注册的虚拟子类的弱引用。
#示例1:Tombola子类的测试运行程序
import doctest
from tombola import Tombola
# 要测试的模块
import bingo, lotto, tombolist, drum #➊
TEST_FILE = 'tombola_tests.rst'
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
def main(argv):
verbose = '-v' in argv
real_subclasses = Tombola.__subclasses__() #➋
virtual_subclasses = list(Tombola._abc_registry) #➌
for cls in real_subclasses + virtual_subclasses: #➍
test(cls, verbose)
def test(cls, verbose=False):
res = doctest.testfile(
TEST_FILE,
globs={
'ConcreteTombola': cls}, #➎
verbose=verbose,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
tag = 'FAIL' if res.failed else 'OK'
print(TEST_MSG.format(cls.__name__, res, tag)) #➏
if __name__ == '__main__':
import sys
main(sys.argv)
➊ 导入包含Tombola真实子类和虚拟子类的模块,用于测试。
➋ __subclasses__()返回的列表是内存中存在的直接子代。即便源码中用不到想测试模块,也要将其导入,因为要把那些类载入内存。
➌ 把_abc_registry(WeakSet对象)转换成列表,这样方能与__subclasses__()的结果拼接起来。
➍ 迭代找到的各个子类,分别传给test函数。
➎ 把cls参数(要测试的类)绑定到全局命名空间里的ConcreteTombola名称上,供doctest使用。
➏ 输出测试结果,包含类的名称、尝试运行的测试数量、失败的测试数量,以及’OK’或’FAIL’标记。
#示例2:TomboList是Tombola的虚拟子类
from random import randrange
from tombola import Tombola
@Tombola.register # ➊
class TomboList(list): # ➋
def pick(self):
if self: # ➌
position = randrange(len(self))
return self.pop(position) # ➍
else:
raise LookupError('pop from empty TomboList')
load = list.extend # ➎
def loaded(self):
return bool(self) # ➏
def inspect(self):
return tuple(sorted(self))
# Tombola.register(TomboList) # ➐
➊ 把Tombolist注册为Tombola的虚拟子类。
➋ Tombolist扩展list。
➌ Tombolist从list中继承__bool__方法,列表不为空时返回True。
➍ pick调用继承自list的self.pop方法,传入一个随机的元素索引。
➎ Tombolist.load与list.extend一样。
➏ loaded方法委托bool函数。
➐ 如果是Python 3.3或之前的版本,不能把.register当作类装饰器使用,必须使用标准的调用句法。
虽然上述示例中可以把register 当作装饰器使用了。但更常见的做法是把它当作函数使用,用于注册其他地方定义的类。
例如,在collections.abc模块的源码中,是这样把内置类型tuple、str、range和memoryview注册为Sequence的虚拟子类的:
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)
其他几个内置类型在_collections_abc.py文件中注册为抽象基类的虚拟子类。这些类型在倒入模块时注册,这样做是可以的,因为必须导入才能使用抽象基类:能访问MutableMapping才能编写isinstance(my_dict,MutableMapping)。
即便不注册,抽象基类也能把一个类识别为虚拟子类。
#示例3: Sized类的源码
class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
if any("__len__" in B.__dict__ for B in C.__mro__): # ➊
return True # ➋
return NotImplemented # ➌
➊ 对C.mro(即C及其超类)中所列的类来说,如果类的__dict__属性中有名为 __len__的属性……
➋ ……返回True,表明C是Sized的虚拟子类。
➌ 否则,返回NotImplemented,让子类检查。