在 Python 的面向对象编程中,很多刚入门的小伙伴,甚至是一些有经验的开发者(比如我这种菜鸟),都会把 __init__ 简称为“构造函数”。
但 Python 中真正的“构造者”其实是 __new__,而 __init__ 只是算是一个“初始化器”。
如果把创建一个对象比作盖房子:
- __new__:可视为施工队,负责把地基打好,把房子的框架建出来,最后把这栋空房子交给你。
- __init__:可视为装修队,负责在已经建好的房子里刷漆、装家具,让房子变得可以居住。
1. 核心差异对比
| 特性 | __new__(cls, ...) | __init__(self, ...) |
|---|---|---|
| 本质类型 | 静态方法 (Static Method) | 实例方法 (Instance Method) |
| 主要职责 | 创建实例并返回 | 初始化已创建的实例 |
| 返回值 | 必须返回一个实例(通常是 cls 的实例) | 必须返回 None |
| 执行时机 | 在 __init__ 之前调用 | 在 __new__ 返回实例之后调用 |
| 常用场景 | 单例模式、继承不可变类型(如 int, tuple) | 设置对象属性、从数据库加载初始数据 |
2. 执行流程图解
当我们执行 obj = MyClass() 时,Python 内部其实经历了一个标准的流水线:
- 调用 MyClass.__new__(MyClass) 得到一个对象实例;
- 检查 __new__ 返回的是否是该类的实例;
- 如果是,则调用该实例的 __init__ 方法进行装饰。
3. 代码演示
让我们通过一段简单的代码来观察它们的调用顺序:
class Demo:
def __new__(cls, *args, **kwargs):
print("1. 执行 __new__:正在分配内存并创建实例")
instance = super().__new__(cls)
return instance
def __init__(self, *args, **kwargs):
print("2. 执行 __init__:正在初始化实例属性")
self.data = args[0]
# 实例化对象
d = Demo("Hello Python")
# 输出结果
# 1. 执行 __new__:正在分配内存并创建实例
# 2. 执行 __init__:正在初始化实例属性
4. 什么时候必须使用 new?
那可能就有小伙伴要发问了:既然 __init__ 能完成大部分工作,为什么还需要 __new__?
主要有以下两个不可替代的场景:
- 继承不可变类型
像 int, str, tuple 这种不可变类型,一旦创建就不能修改。如果我们想自定义一个继承自 int 的类,在 __init__ 中修改值是无效的,因为对象已经创建完成了。
class PositiveInt(int):
def __new__(cls, value):
# 在对象创建之前,将负数强制转为绝对值
return super().__new__(cls, abs(value))
num = PositiveInt(-10)
print(num) # 输出: 10
- 实现单例模式 (Singleton)
单例模式要求一个类只能有一个实例,我们可以通过在 __new__ 中拦截创建过程来实现。
class DatabaseConnection:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
print("创建新连接...")
cls._instance = super().__new__(cls)
else:
print("复用现有连接...")
return cls._instance
# 无论调用多少次,返回的都是同一个对象
db1 = DatabaseConnection() # 输出创建新连接...
db2 = DatabaseConnection() # 输出复用现有连接...
print(db1 is db2) # 输出: True
5. 容易踩的坑
- 忘记返回实例: 如果你在 __new__ 中忘了 return instance,那么 __init__ 永远不会被执行,且你的变量会得到 None;
- 返回值类型不符: 如果 __new__ 返回了另一个类的实例,Python 就不会调用当前类的 __init__;
- 参数匹配: __new__ 和 __init__ 的参数签名通常需要保持一致(或者使用 *args, **kwargs),因为它们接收的实例化参数是一样的。
在一般情况下,我们只需要使用 __init__。但当需要控制对象的创建过程时,__new__ 就是我们手中最强大的武器。