如何创建子类?这似乎不是个问题,通过继承可以定义子类:
class Parent: pass
class Child(Parent): pass
print(issubclass(Child, Parent))
# True
这种通过继承确定的子类关系我们称之为“真实子类”(real subclass)。Python为我们提供了另一种确定子类关系的方式——注册。而这种通过注册确定的子类,我们称为“虚拟子类”(virtual subclass)。两种子类有什么区别呢?而注册又是什么呢?
通过真实子类定义我们可以看到,子类继承自父类,所以拥有父类许多属性,且子类的__bases__
属性保存的是父类。更进一步地,如果父类是抽象基类,且具有抽象方法,那么真实子类必须实现所有的抽象方法,否则不可以实例化。而**虚拟子类是指某个类提供了一个注册方法,可以指定某个其他类为自己的子类。**这样,这个“虚拟子类”除去在issubclass
判断时返回True
,其他任何地方都和所谓的父类没有关系(因为仅仅注册了一下)。在Python中,抽象基类提供了register
方法,允许我们通过注册的方式指明子类的抽象类别:
import abc
class Liquid(abc.ABC): pass
class Rock: pass
print(issubclass(Rock, Liquid))
# False
Liquid.register(Rock)
print(issubclass(Rock, Liquid))
# True
register
方法仅仅让issubclass
能够识别子类,除此之外,其他任何校验也不会做,虚拟子类也不会继承父类的任何东西:
import abc
class Liquid(abc.ABC):
@abc.abstractmethod
def flow(self): pass
class Rock: pass
Liquid.register(Rock)
r = Rock()
r.flow()
# AttributeError: 'Rock' object has no attribute 'flow'
从上例可以看出,虚拟子类不需要实现抽象方法。
抽象基类还提供了装饰器定义虚拟子类的方式:
@Liquid.register
class Rock: pass
print(issubclass(Rock, Liquid))
# True
虚拟子类是抽象基类动态性的体现,也是符合Python风格的方式。它允许我们动态地,清晰地改变类的属别关系。抽象基类定义了一系列方法,并给出了方法应当实现的功能,在这一层次上,“白鹅类型”能够将类进行甄别。当一个类继承自抽象基类时,语言本身限制了该类必须完成抽象基类定义的语义;当一个类注册为虚拟子类时,限制则来自于编写者自身(成年人)。两种类都能通过“白鹅类型”的校验,不过虚拟子类提供了更好的灵活性与扩展性。例如,一个框架允许第三方插件时,采用虚拟子类即可以明晰接口,又不会影响内部的实现。
实际上,标准库中的抽象基类也大量使用了register
的方式来进行分类。例如,在collections.abc
中,抽象基类Sequence
注册了许多内置容器类型作为虚拟子类,这样,当我们的程序中需要一个序列对象时,传递任何内置容器类型都可以正常通过校验:
import collections.abc
a = [1, 2, 3]
if isinstance(a, collections.abc.Sequence):
print('Is Sequence')
# Is Sequence
前一篇文章介绍了如何自定义判断子类的方法,即在自定义元类中重载__subclasscheck__
方法。这个方法最大的不足在于我们必须有一个自定义的元类。当我们在践行“白鹅类型”时,可能不得不定义出元类:
import abc
class GooseMeta(abc.ABCMeta):
def __subclasscheck__(cls, sub):
return True
class Goose(metaclass=GooseMeta): pass
class WhiteGoose: pass
print(issubclass(WhiteGoose, Goose))