Skip to content

Latest commit

 

History

History
97 lines (63 loc) · 3.75 KB

object_oriented26.md

File metadata and controls

97 lines (63 loc) · 3.75 KB

一切皆对象——Python面向对象(二十六):虚拟子类

子类

如何创建子类?这似乎不是个问题,通过继承可以定义子类:

class Parent: pass
class Child(Parent): pass

print(issubclass(Child, Parent))
# True

这种通过继承确定的子类关系我们称之为“真实子类”(real subclass)。Python为我们提供了另一种确定子类关系的方式——注册。而这种通过注册确定的子类,我们称为“虚拟子类”(virtual subclass)。两种子类有什么区别呢?而注册又是什么呢?

真实子类 vs 虚拟子类

通过真实子类定义我们可以看到,子类继承自父类,所以拥有父类许多属性,且子类的__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

WHY虚拟子类

虚拟子类是抽象基类动态性的体现,也是符合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))