__init_subclass__ 是首选方案,因它在子类定义时触发、轻量安全、不干扰实例化且无需元类复杂逻辑。

为什么 __init_subclass__ 是首选方案
Python 3.6+ 提供了更轻量、更直观的禁止继承方式,比自定义元类更安全、更易维护。它在子类定义时触发,能直接抛出异常,且不干扰类本身的实例化逻辑。
常见错误是误以为必须用元类才能控制继承行为——其实 __init_subclass__ 就是为此设计的标准机制。
- 它只在定义子类时调用,不影响父类正常使用
- 不涉及元类复杂的 MRO 和
__new__干预,避免意外覆盖内置行为 - 可配合
**kwargs兼容未来可能的参数扩展
示例:
class SealedClass:
def __init_subclass__(cls, **kwargs):
raise TypeError(f"{cls.__name__} cannot be subclassed")
后续任何 class Child(SealedClass): pass 都会立即报 TypeError,位置精准、信息明确。
元类方案中必须重写 __new__ 而非 __init__
如果坚持用元类(比如需兼容旧版本或做更底层控制),关键点在于:检查和拦截必须发生在类对象创建阶段,即 __new__;在 __init__ 里抛异常已晚——子类对象已经构造完成。
典型坑是把逻辑写在 __init__ 里,结果运行时不报错,但子类实际已存在。
-
__new__接收的是即将创建的类名、父类元组、属性字典,此时可检查bases是否包含禁止继承的类 - 必须显式调用
super().__new__(),否则类无法生成 - 不要在元类中修改
__mro__或强行删除__subclasses__缓存——这些是只读或受解释器保护的
正确写法:
class FinalMeta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
for base in bases:
if hasattr(base, "_sealed") and base._sealed:
raise TypeError(f"Cannot inherit from sealed class {base.__name__}")
return super().__new__(mcs, name, bases, namespace)
class Base(metaclass=FinalMeta):
_sealed = True
被禁继承的类仍可被 isinstance 和 issubclass 正常使用
无论用 __init_subclass__ 还是元类,限制仅作用于「定义子类」这一动作,不影响已有类型检查逻辑。这点常被忽略,导致误以为封禁后类型系统会断裂。
-
isinstance(obj, SealedClass)依然返回True(只要 obj 是其实例) -
issubclass(SealedClass, object)仍为True,因为它是object的子类 - 但
issubclass(Child, SealedClass)根本不会执行——Child定义失败,压根没有这个类对象
换句话说:封禁发生在编译/定义期,不是运行期的动态拦截。
多重继承下需注意基类顺序和检查粒度
当一个类同时继承多个父类,而其中某一个是被封禁的,Python 会按 bases 元组顺序依次检查。如果封禁逻辑只查第一个父类,就可能漏掉后面被禁的基类。
尤其在元类方案中,若只检查 bases[0],而真实被禁类在 bases[1],子类就能绕过限制。
- 务必遍历全部
bases,不能假设单继承 - 若用
__init_subclass__,因它是每个父类分别触发的,天然覆盖所有路径——只要任一父类禁止,就会报错 - 在复杂框架中(如 Django Model 继承链),建议给封禁类加唯一标记属性(如
_final = True),而非依赖类名字符串匹配
封禁逻辑越早失败越好,但前提是检查要全量、无遗漏。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











