ClassName() 只是一步?拆开看 __new__ 和 __init__ 各自在干什么

「Python 进阶之路」系列 Day12

写在前面

ClassName(...) 这行代码看起来是"一步创建对象",实际上背后拆成了两个独立的阶段。上一篇讲类变量和实例变量,本质是"属性归谁"的问题;今天这篇往前退一步,讲清楚一个对象到底是怎么"被造出来"的------__new____init__ 各自负责什么,以及这个区分在单例模式、不可变类型子类化里为什么会变得很关键。


一、是什么:对象创建的两个阶段

  • __new__(cls, ...) :真正负责"创造"这个对象,返回一个实例。它的第一个参数是 cls(类本身),本质上是一个隐式的 staticmethod
  • __init__(self, ...) :负责对已经创建好的对象做初始化(设置属性)。第一个参数是 self,此时对象已经存在了
python 复制代码
class Foo:
    def __new__(cls, *args, **kwargs):
        print("1. __new__ 被调用,负责创建实例,cls =", cls)
        instance = super().__new__(cls)
        return instance
    def __init__(self, name):
        print("2. __init__ 被调用,负责初始化,self =", self)
        self.name = name

f = Foo("test")
# 1. __new__ 被调用,负责创建实例,cls = <class '__main__.Foo'>
# 2. __init__ 被调用,负责初始化,self = <__main__.Foo object at 0x...>

验证 __new__ 确实是隐式的 staticmethod

python 复制代码
print(type(Foo.__dict__["__new__"]))   # <class 'staticmethod'>

二、为什么要拆成两步:new 返回值决定一切

flowchart LR A[调用ClassName传参数] --> B[调用new创建对象<br/>cls作为第一个参数] B --> C{new返回的<br/>是不是该类实例} C -->|是| D[调用init初始化<br/>self就是new返回的对象] C -->|否| E[跳过init<br/>直接返回new的结果]

关键点:__new__ 如果不返回该类(或子类)的实例,__init__ 根本不会被调用

python 复制代码
class Bar:
    def __new__(cls, *args, **kwargs):
        print("Bar.__new__ 被调用")
        return "不是Bar的实例,是个字符串"   # 故意返回别的类型
    def __init__(self, *args, **kwargs):
        print("Bar.__init__ 被调用!")   # 这行根本不会执行

result = Bar("随便什么参数")
print(result, type(result))
# Bar.__new__ 被调用
# 不是Bar的实例 <class 'str'>

这解释了为什么要把"创建"和"初始化"拆成两个独立的阶段:__new__ 有权决定这次调用到底要不要真的产生一个新对象、产生哪个类的对象 ,而 __init__ 只是在"确实产生了一个本类实例"这个前提下才会被触发去做初始化。这个控制权正是单例模式、不可变类型子类化这些进阶用法的基础。


三、怎么用

1. 应用一:单例模式

通过重写 __new__,让类只在第一次调用时真正创建实例,之后每次调用都复用同一个:

python 复制代码
class Singleton:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            print("第一次创建,真正调用 super().__new__")
            cls._instance = super().__new__(cls)
        else:
            print("已经有实例了,直接复用")
        return cls._instance
    def __init__(self, value=None):
        print("__init__ 被调用,value =", value)
        if value is not None:
            self.value = value

s1 = Singleton("第一次的值")
s2 = Singleton("第二次的值")
print(s1 is s2)            # True ------ 确实是同一个实例
print(s1.value, s2.value)   # 第二次的值 第二次的值

容易被忽略的细节:即使 __new__ 复用了已有实例,__init__ 每次调用 Singleton(...) 时依然会被执行 ------上面例子里 s1.value 最终变成了"第二次的值",因为第二次调用时 __init__ 又把 value 覆盖了一遍。如果只想在真正创建实例的那一次执行初始化逻辑,需要在 __init__ 里自己加判断(比如检查一个标记位),__new__/__init__ 分离并不会自动帮你避免重复初始化。

2. 应用二:不可变类型子类化

继承 int/str/tuple 这类不可变类型时,值必须在 __new__ 阶段就确定 ,因为对象一旦被创建(__new__ 返回后),这个值就不可能再被修改,__init__ 阶段已经来不及了:

python 复制代码
class PositiveInt(int):
    def __new__(cls, value):
        if value <= 0:
            raise ValueError("必须是正数")
        return super().__new__(cls, value)   # int的值必须在创建时(__new__)就定好

p = PositiveInt(5)
print(p, type(p), p + 1)   # 5 <class '__main__.PositiveInt'> 6

PositiveInt(-1)
# ValueError: 必须是正数

用一个反例验证"__init__ 阶段已经无法修改不可变值":

python 复制代码
class TryModifyInInit(int):
    def __init__(self, value):
        super().__init__()
        print(f"__init__ 里 self 的值已经是 {int(self)},无法在这里再改变它")

t = TryModifyInInit(42)
print(t)   # 42 ------ 值早在 __new__ 阶段(也就是 int.__new__)就已经定死了

这就是为什么校验/初始化不可变类型的值,必须写在 __new__ 里,而不是像普通类那样习惯性地写在 __init__ 里。


四、面试追问

Q1:__new____init__ 的区别是什么?

__new__(cls, ...) 负责真正创建并返回一个实例,第一个参数是 cls,本质是一个隐式的 staticmethod__init__(self, ...) 负责对已经创建好的实例做初始化(设置属性),第一个参数是 self,不需要也不能有返回值。

Q2:对象实例化的完整过程是什么?

调用 ClassName(...) 时,先调用 __new__(cls, ...) 得到一个对象;如果这个对象确实是该类(或其子类)的实例,接下来才会调用它的 __init__(...) 做初始化;如果 __new__ 返回的不是本类实例,__init__ 根本不会被调用,ClassName(...) 的最终结果就是 __new__ 返回的那个对象。

Q3:单例模式怎么用 __new__ 实现?

用一个类变量(如 _instance)记录是否已经创建过实例,在 __new__ 里判断:如果还没创建过,就真正调用 super().__new__(cls) 创建并缓存下来;如果已经创建过,直接返回缓存的那个实例。需要注意的是,即使复用了已有实例,__init__ 在每次调用类名时依然会被执行,如果不想重复初始化,需要自己在 __init__ 里加判断。

Q4:为什么继承不可变类型时经常需要重写 __new__ 而不是 __init__

因为不可变类型(int/str/tuple 等)的值必须在对象创建的那一刻就确定下来,__new__ 返回之后这个对象的值就已经"定型"、无法再被修改,__init__ 阶段这个对象已经创建完毕,任何试图在 __init__ 里修改值的操作都不会生效,所以校验和构造逻辑必须写在 __new__ 里。

Q5:__new__ 不返回该类实例时会发生什么?

__init__ 根本不会被调用。ClassName(...) 这次调用的最终结果,就是 __new__ 返回的那个对象,它可以是任意类型(不一定是 ClassName 的实例),Python 不会强行去调用一个"类型不匹配"的对象的 __init__ 方法。


下一篇预告

Day13 讲多重继承与 MRO------当一个类同时继承多个父类时,方法调用顺序到底是怎么确定的,C3 线性化算法具体在算什么。

相关推荐
geovindu1 小时前
java: Memento Pattern
java·开发语言·后端·备忘录模式·行为模式
星哥的编程之路1 小时前
万字深度解析 Agent 学习路线
后端
程序边界1 小时前
SQL Server数据迁移这件事,远比你想的复杂——但也远比你想的简单(下)
后端
曾阿伦1 小时前
Trae CN Python环境调试debug指南
开发语言·python
Zane19941 小时前
JRE 去哪了?一次讲清楚 JDK、JRE、JVM 到底是什么关系?
java·后端
JavaPub1 小时前
Ontology 本体论是什么?从哲学概念到 AI、知识图谱与软件工程
后端
liuxiaocheng1 小时前
聊聊 Vercel AI SDK 的流式协议:前后端到底是怎么"边想边说"的
前端·后端
渣男教父1 小时前
Python游戏自动化实战案例
python·编程语言
得物技术2 小时前
得物知识问答:复合检索 Agent 的系统设计实践
人工智能·后端·ai编程