Python __new__与__init__的区别及执行顺序详解
深入详解 Python 中 __new__ 与 __init__ 的本质区别、调用执行顺序、参数传递机制及实际开发应用场景。通过完整的图解流程与可运行示例,彻底扫清面向对象实例化中的核心盲区。
__new__ 和 __init__ 协同工作的双阶段过程。简而言之:__new__ 负责“构造”(分配内存并返回对象实例),属于静态方法;__init__ 负责“初始化”(为刚刚创建好的实例绑定属性),属于实例方法。调用顺序永远是 __new__ 在前、__init__ 在后。本文将深入剖析底层机制、参数流动、触发条件及实战场景。
一、为什么我们需要区分构造与初始化?
在很多初学者看来,Python 中的 __init__ 经常被称为“构造函数”(Constructor)。但在严格的语言规范中,这种称呼并不准确。__init__ 仅仅是初始化器(Initializer)。
当你在代码中写下 obj = MyClass("arg") 时,内存中此时还没有该对象的实体。在 __init__ 执行之前,必须先有某种机制在内存中为对象划分地址空间、建立对象类型指针。这个从无到有的“造物”工作,正是由 __new__ 独立承担的。
二、核心区别全方位对比
为了清晰区分二者的技术边界,我们可以从方法签名、返回值、调用身份及典型应用四个维度进行对照:
| 对比维度 | __new__(cls, *args, **kwargs) |
__init__(self, *args, **kwargs) |
|---|---|---|
| 职责定位 | 对象的“创建者”(分配内存、构造底层结构) | 对象的“装饰者”(初始化属性、配置状态) |
| 首位参数 | cls(当前所要创建的类本身) |
self(已经被 __new__ 创建出来的实例引用) |
| 方法类型 | 静态方法(底层为 CPython 特化静态函数,无需加 @staticmethod) | 常规实例方法 |
| 返回值要求 | 必须显式返回一个对象实例(通常为 super().__new__(cls)) |
严禁返回值(必须返回 None,显式返回非 None 会抛 TypeError) |
| 重写频率 | 极少重写(仅在元编程、单例模式或不可变类型派生时使用) | 极高重写(日常面向对象开发中最常用的方法) |
| 执行时间点 | 实例化阶段最先调用 | 在 __new__ 返回本类实例后紧随其后执行 |
三、执行顺序代码验证:谁先谁后?
我们通过一段最直观的代码来观察二者的实际执行顺序和参数流动:
class Demo:
def __new__(cls, *args, **kwargs):
print(f"[1] __new__ 执行:cls 是 {cls},接收参数: {args}")
instance = super().__new__(cls)
print(f"[2] __new__ 生成实例引用: {id(instance)}")
return instance
def __init__(self, name):
print(f"[3] __init__ 执行:self 实例引用: {id(self)}")
self.name = name
print(f"[4] __init__ 完成属性绑定: self.name = {self.name}")
# 触发实例化
d = Demo("Python工匠")
运行结果示例:
[1] __new__ 执行:cls 是 <class '__main__.Demo'>,接收参数: ('Python工匠',)
[2] __new__ 生成实例引用: 4329188432
[3] __init__ 执行:self 实例引用: 4329188432
[4] __init__ 完成属性绑定: self.name = Python工匠
从输出可以观察到两点极其关键的事实:
- 执行顺序严格递进:先打印
[1]和[2],后打印[3]和[4]。 - 实例引用的连续性:
__new__生成的id(instance)与传递进__init__的id(self)完全一致,证实了self正是__new__的返回值。
关键转折实验:如果 __new__ 返回了非本类对象?
这是 Python 试题与实战中最容易被忽视的机制。根据 Python 官方规范:只有当 __new__ 返回当前类 cls 的实例时,Python 才会调用 __init__。
class Other:
pass
class Strange:
def __new__(cls):
print("Strange.__new__ 被调用")
# 故意返回 Other 类的实例
return Other()
def __init__(self):
print("Strange.__init__ 被调用(此行不会执行)")
s = Strange()
print("最终获得对象类型:", type(s))
运行结果示例:
Strange.__new__ 被调用
最终获得对象类型: <class '__main__.Other'>
可以看到,Strange.__init__ 根本没有被触发!这是因为 Python 解释器发现 __new__ 返回的对象不是 Strange 的实例,因此认为对其调用 Strange.__init__ 是不安全的。
四、__new__ 的两大经典实战场景
场景 1:继承不可变内置类型(int, str, tuple, float)
在 Python 中,int、str、tuple 等是不可变对象(Immutable Objects)。一旦创建完成,其底层的内存数值就无法更改。如果你希望自定义一个大写字符串类型,在 __init__ 中进行字符串转换是毫无意义的,因为字符串在传入 __init__ 之前就已经固化。
实现自定义大写字符串的代码示例:
class UpperStr(str):
def __new__(cls, value):
# 在对象生成前将原始字符串转换为大写
upper_val = str(value).upper()
# 委托父类 str 生成不可变实例
return super().__new__(cls, upper_val)
s = UpperStr("antigravity")
print(s) # 输出: ANTIGRAVITY
print(type(s)) # 输出: <class '__main__.UpperStr'>
场景 2:实现单例模式(Singleton Pattern)
单例模式要求某个类在整个进程中只存在一个实例。如果由 __init__ 负责控制,由于 __init__ 被调用时对象早已在内存中创建完毕,根本无法阻止新对象的生成。
通过重写 __new__,我们可以在分配内存之前检查类属性中是否已存在缓存的实例。如果存在,则直接返回该缓存引用;只有在首次调用时才真正创建:
class DatabasePool:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
# 首次实例化:调用父类分配内存
cls._instance = super().__new__(cls)
# 后续实例化:直接返回同一实例引用
return cls._instance
def __init__(self, host="127.0.0.1"):
self.host = host
p1 = DatabasePool("192.168.1.10")
p2 = DatabasePool("10.0.0.1")
print("是否为同一实例:", p1 is p2) # 输出: True
print("p1 内存地址:", id(p1))
print("p2 内存地址:", id(p2))
__init__ 会被反复执行!注意上面的代码:尽管
p1 is p2 确实为 True,但由于 __new__ 每次返回的都是 DatabasePool 的实例,Python 解释器在每次执行 DatabasePool(...) 时都会再次调用 __init__,导致 self.host 被后来的参数覆盖!解决办法是在类内部增加一个 _initialized 标志,在 __init__ 中判断若已初始化则直接 return。
五、开发中极易踩坑的三大注意事项
-
注意 1:
__init__严禁返回任何非None值
写def __init__(self): return self会直接触发TypeError: __init__() should return None, not '...'。因为__init__的职责就是静默填充属性,不需要也不允许向调用方传递新对象。 -
注意 2:
object.__new__的参数陷阱
在继承自object(Python 3 中的所有新式类)时,如果不重写__init__,object.__new__(cls, *args, **kwargs)会报错拒绝接收额外参数。因此在自定义__new__中通常只写super().__new__(cls),而不把*args, **kwargs往下传给object.__new__。 -
注意 3:不要混淆类方法与静态方法
__new__在形式上接收cls,很像@classmethod,但在底层 CPython 中它是作为特殊静态方法(static method with special lookup)处理的,无需在方法前添加@staticmethod或@classmethod装饰器。
六、总结
__new__ 和 __init__ 共同构成了 Python 优雅而完备的对象构建体系:
在绝大多数日常业务开发中,我们只需要使用 __init__ 完成常规的状态挂载与依赖注入;但在需要精细控制对象构造、实现高性能单例或扩展不可变核心类型的底层库开发中,深刻理解 __new__ 的运行机制与执行时机,将赋予我们直接操纵对象创生阶段的强大能力。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













