在 __init__ 中抛异常无法真正阻止实例化,因为 __new__ 已完成对象创建;必须在 __new__ 中抛出异常(如 TypeError)才能从源头拦截。

为什么在 __init__ 里抛异常不能真正阻止实例化
直接在 __init__ 中 raise 异常,确实会让构造调用失败,但对象其实已经创建出来了——__new__ 已执行完毕,内存已分配,__init__ 只是初始化阶段。这意味着:
• 你仍可能拿到一个“半残”对象(比如通过 __new__ 返回缓存或已有实例);
• 若类被继承,子类可能绕过你的 __init__ 检查;
• 调试时看到 TypeError 或 RuntimeError,但栈里仍有 object.__new__ 的痕迹,容易误判为“没创建”。
真正禁用实例化的正确位置是 __new__
__new__ 是实例诞生的第一道关卡,控制它才能从源头拦截。只要它不返回本类实例(或其子类),Python 就不会继续调用 __init__。
常见写法:
class UtilityClass:
def __new__(cls, *args, **kwargs):
raise TypeError(f"{cls.__name__} cannot be instantiated")
注意点:
• 必须用 raise,不能返回 None(否则会报 TypeError: __new__ should return an instance);
• 错误类型建议用 TypeError,这是 Python 内置对“非法调用构造器”的标准响应;
• 如果类有元类或继承自 Enum/NamedTuple 等特殊基类,需额外确认 __new__ 是否被覆盖或代理。
立即学习“Python免费学习笔记(深入)”;
和 @staticmethod / @classmethod 混用时的陷阱
有人想“既禁止实例化,又提供工具方法”,于是写:
class MathUtils:
def __new__(cls):
raise TypeError("MathUtils is not instantiable")
@staticmethod
def add(a, b):
return a + b
这本身没问题,但要注意:
• 静态方法不依赖实例,所以安全;
• 类方法(@classmethod)第一个参数是 cls,而 cls 仍可能被误用于尝试实例化(比如子类重写了 __new__);
• 更稳妥的做法是把工具函数拆到模块顶层,或用 typing.final + 文档注明“仅作命名空间使用”;
• 如果坚持用类封装,建议加 __init_subclass__ 阻止继承:def __init_subclass__(cls, **kwargs): raise TypeError(f"{cls.__name__} must not be subclassed")。
检查是否真被禁用:别只信异常信息
验证时别只看有没有报错,要确认对象是否真的没产生:
- 用
id()或isinstance(obj, YourClass)测试——如果异常抛出前已有对象被创建并赋值,说明__new__没拦住; - 在
__new__开头加print("in __new__"),再运行MathUtils(),应只看到 print 和异常,没有后续__init__的输出; - 若用 IDE 自动补全或静态检查(如 mypy),记得加
typing.NoReturn注解到__new__返回类型,帮助工具识别该路径无正常返回。
最易忽略的是:子类默认继承父类的 __new__,但一旦子类自己定义了 __new__,就可能无意中放行——禁用逻辑必须显式在每层生效,或靠元类统一管控。


















