__init__ 不是构造函数,而是初始化方法,实例由 __new__ 创建;若其中抛异常会残留半初始化对象,导致状态不一致或 AttributeError;不可返回值,耗时操作应避免,参数设计需严谨,调用子类方法需谨慎。

__init__ 是实例创建后才调用的,不是构造函数
Python 里没有传统意义上的“构造函数”,__new__ 才负责创建实例,__init__ 只是做初始化。如果你在 __init__ 里抛异常(比如参数校验失败),实例其实已经造出来了,只是没来得及返回给变量——这会导致内存里残留一个半初始化对象,调试时可能看到 __dict__ 已部分赋值但状态不一致。
常见错误现象:AttributeError 在后续方法里突然冒出,但 __init__ 看似走完了;或者日志显示 __init__ 被调用了,但 print(self) 却输出异常对象。
- 初始化逻辑必须假设
self已存在,但属性可能为空或默认值 - 不要在
__init__里做耗时操作(如读文件、连数据库),它阻塞实例化过程 - 若需控制实例创建(比如单例、类型检查),得重写
__new__,而不是硬扛在__init__里
参数设计:self 之后的第一个参数决定是否支持位置传参
__init__ 的参数签名直接影响使用者怎么调用你的类。比如 def __init__(self, name, age=0) 允许 User("Alice") 或 User("Alice", 25);但加了 * 或 ** 就得小心默认值和顺序。
使用场景:写工具类时,常把必填参数放前面,可选配置用 **kwargs 收口;但别滥用 **kwargs,否则 IDE 补全失效,调用方也搞不清哪些键名合法。
立即学习“Python免费学习笔记(深入)”;
- 必填参数没设默认值 → 实例化时漏传会直接报
TypeError: __init__() missing 1 required positional argument - 带默认值的参数必须放在无默认值参数之后,否则语法错误
- 如果想让某些参数只能用关键字传(比如避免误传顺序),用
*分隔符:def __init__(self, name, *, age=None, active=True)
别在 __init__ 里调用可被子类覆盖的方法
子类重写父类方法很常见,但如果父类 __init__ 里调用了那个方法,而子类还没初始化完就执行了,容易出错。典型例子是调用 self.setup() 或 self.validate(),结果子类的 setup 依赖某个还没赋值的属性。
性能影响:看似只是函数调用,但多一层虚函数查找 + 子类方法可能触发额外计算或 I/O,拖慢实例化。
- 优先用具体逻辑,而不是抽象方法调用;真要拆,改用私有方法(如
self._do_setup()) - 如果必须调用子类实现,确保该方法只读、无副作用,且文档明确标注“仅限 __init__ 中安全调用”
- 更稳妥的做法是把这类逻辑推迟到首次访问属性时(用
@property或__getattr__)
__init__ 返回值被忽略,别试图 return 东西
__init__ 必须返回 None,哪怕你写了 return self 或 return "ok",Python 也会静默丢弃,不会报错,但行为完全不符合直觉。
容易踩的坑:从其他语言转过来的人习惯在构造器里 return 实例,结果发现代码跑通了但逻辑没生效;或者用装饰器包装 __init__ 时误以为能拦截返回值。
- 如果需要“返回不同实例”,必须用
__new__控制返回对象 - 如果想做初始化后校验并拒绝实例,只能抛异常(如
raise ValueError("age must be > 0")) - 装饰
__init__时,别在装饰器里 return 非None,否则会触发TypeError: __init__() should return None
真正麻烦的是那些隐式触发 __init__ 的场景:比如 copy.copy()、dataclasses.replace()、甚至某些 ORM 加载记录时——它们都依赖 __init__ 正常工作,但你很难在这些上下文里调试初始化逻辑。


















