在昨天的文章里面,我们讲到了,当子类试图覆盖父类的时候,可以通过类型标注来发出警告。今天,我们来讲讲如何直接禁止覆盖。
Python 原生是没有提供禁止子类覆盖父类的方法的功能,因此我们需要自己来实现。
先来看一下实现效果:
在这段代码里面,我们禁止子类覆盖父类的dead()
和eat()
方法,但不禁止move
方法。所以,当我们在子类Dog
里面尝试覆盖父类中的dead()
时,程序就报错了。具体要覆盖哪些方法,可以在定义类的时候指定,传入的参数metaclass=protect('方法1', '方法2', '方法3', ...)
就可以了。
那么这个protect
函数是个什么东西呢?我们来看看它的代码:
def protect(*protected): """Returns a metaclass that protects all attributes given as strings""" class Protect(type): has_base = False def __new__(meta, name, bases, attrs): if meta.has_base: for attribute in attrs: if attribute in protected: raise AttributeError('Overriding of attribute "%s" not allowed.'%attribute) meta.has_base = True klass = super().__new__(meta, name, bases, attrs) return klass return Protect
这里,用到了 Python 的元类
。如果大家对元类有兴趣,可以看9.13 使用元类控制实例的创建 — python3-cookbook 3.0.0 文档[1]。简单的来说,元类用来定义类的创建行为。它一般的格式为:
class 类名(metaclass=另一个类): ...
而大家看我们用来禁止重试的这个函数protect
,它返回的就是一个Protect
类。这个类继承于type
对象。
Protect
类有一个__new__
方法,这个方法会在使用了元类的所有子类的__init__
之前被调用。在__new__
里面,我们拿到了子类要定义的方法,并且检查他们是不是在我们传给protect
的列表里面。如果在,说明这个方法不能被覆盖。
当实现我们自己的父类Animal
的时候,由于meta.has_base
为 False
,所以不会触发检查逻辑。但当我们基于Animal
实现Dog
子类的时候,由于meta.has_base
是True
,所以进入检查逻辑。Dog
的所有方法名都在attrs
参数里面。循环检查每一个方法名是否在禁止的列表中,如果在,就抛出异常。如果不在,就继续后面的创建过程。
元类在理解上可能比较困难。如果大家无法理解上面这一段也没有关系,直接用就是了。
参考文献
[1] 9.13 使用元类控制实例的创建 — python3-cookbook 3.0.0 文档: https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p13_using_mataclass_to_control_instance_creation.html
请关注微信公众号【未闻Code】获取更多精彩文章。