`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 返回值决定一切

#mermaid-svg-2ghbvstAAUZbhIxJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2ghbvstAAUZbhIxJ .error-icon{fill:#552222;}#mermaid-svg-2ghbvstAAUZbhIxJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2ghbvstAAUZbhIxJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2ghbvstAAUZbhIxJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2ghbvstAAUZbhIxJ .marker.cross{stroke:#333333;}#mermaid-svg-2ghbvstAAUZbhIxJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2ghbvstAAUZbhIxJ p{margin:0;}#mermaid-svg-2ghbvstAAUZbhIxJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ .cluster-label text{fill:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ .cluster-label span{color:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ .cluster-label span p{background-color:transparent;}#mermaid-svg-2ghbvstAAUZbhIxJ .label text,#mermaid-svg-2ghbvstAAUZbhIxJ span{fill:#333;color:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ .node rect,#mermaid-svg-2ghbvstAAUZbhIxJ .node circle,#mermaid-svg-2ghbvstAAUZbhIxJ .node ellipse,#mermaid-svg-2ghbvstAAUZbhIxJ .node polygon,#mermaid-svg-2ghbvstAAUZbhIxJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2ghbvstAAUZbhIxJ .rough-node .label text,#mermaid-svg-2ghbvstAAUZbhIxJ .node .label text,#mermaid-svg-2ghbvstAAUZbhIxJ .image-shape .label,#mermaid-svg-2ghbvstAAUZbhIxJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-2ghbvstAAUZbhIxJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2ghbvstAAUZbhIxJ .rough-node .label,#mermaid-svg-2ghbvstAAUZbhIxJ .node .label,#mermaid-svg-2ghbvstAAUZbhIxJ .image-shape .label,#mermaid-svg-2ghbvstAAUZbhIxJ .icon-shape .label{text-align:center;}#mermaid-svg-2ghbvstAAUZbhIxJ .node.clickable{cursor:pointer;}#mermaid-svg-2ghbvstAAUZbhIxJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2ghbvstAAUZbhIxJ .arrowheadPath{fill:#333333;}#mermaid-svg-2ghbvstAAUZbhIxJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2ghbvstAAUZbhIxJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2ghbvstAAUZbhIxJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2ghbvstAAUZbhIxJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2ghbvstAAUZbhIxJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2ghbvstAAUZbhIxJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2ghbvstAAUZbhIxJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2ghbvstAAUZbhIxJ .cluster text{fill:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ .cluster span{color:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2ghbvstAAUZbhIxJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2ghbvstAAUZbhIxJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-2ghbvstAAUZbhIxJ .icon-shape,#mermaid-svg-2ghbvstAAUZbhIxJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2ghbvstAAUZbhIxJ .icon-shape p,#mermaid-svg-2ghbvstAAUZbhIxJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2ghbvstAAUZbhIxJ .icon-shape .label rect,#mermaid-svg-2ghbvstAAUZbhIxJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2ghbvstAAUZbhIxJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2ghbvstAAUZbhIxJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2ghbvstAAUZbhIxJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

调用ClassName传参数
调用new创建对象

cls作为第一个参数
new返回的

是不是该类实例
调用init初始化self

就是new返回的对象
跳过init

直接返回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 线性化算法具体在算什么。

相关推荐
人邮异步社区1 小时前
如何系统地学习 C++ 语言?
开发语言·c++·学习
GlueNa2SiO31 小时前
04-Flask数据库操作SQLAlchemy
笔记·python·flask
知识分享小能手1 小时前
概理论与数理统计学习教程,从入门到精通,在数理统计中应用R软件(21)
开发语言·学习·r语言
Data_Journal1 小时前
Playwright vs Selenium:哪个是最佳无头浏览器
开发语言·python·scrapy·microsoft·编辑器
Chester_19991 小时前
CSP202104C.DHCP服务器
开发语言·程序人生·蓝桥杯
跨境技工小黎1 小时前
LLM蜜罐系统是什么?AI爬虫如何精准绕过?
开发语言·人工智能·python
lskblog1 小时前
Ubuntu 18.04 安装 LibreOffice(用于 Word 在线预览转换)
java·linux·python·ubuntu·word·php·laravel
-今昭-1 小时前
OpenStack Dashboard 部署与图形化创建云主机实战指南
开发语言·php
王志来137944730081 小时前
多元场景催生工控服务器机箱差异化需求匀天以柔性适配回应行业挑战
运维·服务器·人工智能·python