Python 中 __new__ 和 __init__ 的区别以及容易踩的坑

42 阅读3分钟

在 Python 的面向对象编程中,很多刚入门的小伙伴,甚至是一些有经验的开发者(比如我这种菜鸟),都会把 __init__ 简称为“构造函数”。

downloaded-image.png

但 Python 中真正的“构造者”其实是 __new__,而 __init__ 只是算是一个“初始化器”。

如果把创建一个对象比作盖房子

  • __new__:可视为施工队,负责把地基打好,把房子的框架建出来,最后把这栋空房子交给你。
  • __init__:可视为装修队,负责在已经建好的房子里刷漆、装家具,让房子变得可以居住

1. 核心差异对比

特性__new__(cls, ...)__init__(self, ...)
本质类型静态方法 (Static Method)实例方法 (Instance Method)
主要职责创建实例并返回初始化已创建的实例
返回值必须返回一个实例(通常是 cls 的实例)必须返回 None
执行时机在 __init__ 之前调用在 __new__ 返回实例之后调用
常用场景单例模式、继承不可变类型(如 int, tuple)设置对象属性、从数据库加载初始数据

2. 执行流程图解

当我们执行 obj = MyClass() 时,Python 内部其实经历了一个标准的流水线:

  1. 调用 MyClass.__new__(MyClass) 得到一个对象实例;
  2. 检查 __new__ 返回的是否是该类的实例;
  3. 如果是,则调用该实例的 __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__?

image.png

主要有以下两个不可替代的场景:

  • 继承不可变类型

像 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. 容易踩的坑

  1. 忘记返回实例: 如果你在 __new__ 中忘了 return instance,那么 __init__ 永远不会被执行,且你的变量会得到 None;
  2. 返回值类型不符: 如果 __new__ 返回了另一个类的实例,Python 就不会调用当前类的 __init__;
  3. 参数匹配: __new__ 和 __init__ 的参数签名通常需要保持一致(或者使用 *args, **kwargs),因为它们接收的实例化参数是一样的。

在一般情况下,我们只需要使用 __init__。但当需要控制对象的创建过程时,__new__ 就是我们手中最强大的武器。