前言
在 Python 中,魔法方法(也叫特殊方法)以双下划线开头和结尾,例如 init、new、str 等。它们赋予了类许多"隐形"的能力,让我们能够像操作内置类型一样操作自定义对象。
当谈到对象创建时,new 和 init 是两个最容易混淆的魔法方法。很多人以为 init 就是构造函数,其实严格来说,new 才是真正的"构造"方法,而 init 是"初始化"方法。
本文将深入剖析 new 与 init 的区别、调用顺序、参数以及各自的使用场景,通过代码示例,帮助你彻底掌握它们。
一、 对象创建的过程
在 Python 中,当我们执行 obj = MyClass(arg1, arg2) 时,解释器内部经历了以下两个步骤:
-
调用 new:分配内存,创建一个新的实例对象(通常返回该实例)。
-
调用 init:如果 new 返回的是 MyClass 的实例,则自动调用 init 对其进行初始化,否则 init 不会被调用。
整个过程可以理解为:
python
python
def __call__(cls, *args, **kwargs):
instance = cls.__new__(cls, *args, **kwargs)
if isinstance(instance, cls):
cls.__init__(instance, *args, **kwargs)
return instance
所以 new 负责"造出"对象,init 负责"装修"对象。
二、 new 详解
2.1 定义与参数
new 是一个静态方法(但不需要显式声明),它的第一个参数是类 cls,后面跟的是传递给构造器的参数。
python
python
class MyClass:
def __new__(cls, *args, **kwargs):
print("__new__ 被调用")
instance = super().__new__(cls) # 调用父类的 __new__
return instance
2.2 返回值
new 必须返回一个实例对象。通常返回的是本类(cls)的实例,但也可以返回其他类的实例。
如果返回的是 cls 的实例,Python 会自动调用 init 初始化它。
如果返回的是其他类的实例,则不会调用 init(因为对象类型不匹配)。
2.3 主要作用
分配内存:创建并返回一个新对象(类似其他语言的 new 关键字)。
控制实例创建:可以实现单例模式、缓存实例、返回已有实例等。
三、 init 详解
3.1 定义与参数
init 的第一个参数是实例 self,后面跟的是构造器传入的参数。它不返回任何值(或者说返回 None)。
python
python
class MyClass:
def __init__(self, value):
print("__init__ 被调用")
self.value = value
3.2 主要作用
初始化实例属性,为实例设置初始状态,进行必要的校验或默认值处理。
3.3 注意事项
init 只有在 new 返回的是本类实例时才会被调用。
如果 new 返回了非本类实例,init 不会执行。
四、new 与 init 的区别对比
|--------|-------------------------|------------------------|
| 特性 | new | init |
| 调用时机 | 在对象创建时最先调用 | 在 new 返回本类实例后调用 |
| 参数 | 第一个参数是类 `cls`,其余为构造参数 | 第一个参数是实例 self,其余为构造参数 |
| 返回值 | 必须返回一个实例(可以是任意类的实例) | 必须返回 None(不返回或返回 None) |
| 主要职责 | 创建并返回一个新实例(控制实例生成) | 初始化已创建的实例(设置属性等) |
| 使用场景 | 单例模式、不可变类型子类、缓存等 | 常规的属性初始化 |
| 是否必须实现 | 通常不需要,除非需要控制实例创建 | 通常都会实现,用来初始化属性 |
五、 典型使用场景
5.1 单例模式
单例模式确保一个类只有一个实例。通过重写 new 来控制实例的创建。
python
python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
print("创建新实例")
cls._instance = super().__new__(cls)
else:
print("返回已有实例")
return cls._instance
def __init__(self, value):
self.value = value
s1 = Singleton(10)
s2 = Singleton(20)
print(s1 is s2) # True
print(s1.value, s2.value) # 20 20(因为__init__每次都会调用,所以第二次覆盖了value)
注意:上面的例子中 init 每次都会执行,导致第二次修改了 value。如果希望保持第一次的值,可以在 init 中加入判断是否已初始化。
python
python
class Singleton:
_instance = None
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
if not self._initialized:
self.value = value
self._initialized = True
5.2 不可变类型的子类
当继承不可变类型(如int、str、tuple)时,new 是修改值的唯一机会,因为不可变对象在创建后无法修改。
python
python
class PositiveInteger(int):
"""只接受正整数的整数子类"""
def __new__(cls, value):
if value <= 0:
raise ValueError("必须是正整数")
# 通过 super().__new__ 创建 int 实例
return super().__new__(cls, value)
def __init__(self, value):
# 这里 value 已经不可变,无需额外操作
super().__init__()
try:
p = PositiveInteger(10)
print(p) # 10
n = PositiveInteger(-5) # 抛出 ValueError
except ValueError as e:
print(e)
5.3 缓存实例(对象池)
有时我们希望重复使用相同参数的实例,避免重复创建。可以通过 new 实现缓存。
python
python
class Student:
_cache = {}
def __new__(cls, name):
if name in cls._cache:
print(f"从缓存返回 {name}")
return cls._cache[name]
else:
print(f"创建新对象 {name}")
instance = super().__new__(cls)
cls._cache[name] = instance
return instance
def __init__(self, name):
self.name = name
s1 = Student("张三")
s2 = Student("张三")
s3 = Student("李四")
print(s1 is s2) # True
print(s1 is s3) # False
5.4 返回其他类的实例
new 可以返回任何类的实例,这在某些元编程场景中有用。
python
python
class A:
def __new__(cls):
return B()
def __init__(self):
print("A 的 __init__ 不会执行")
class B:
def __init__(self):
print("B 的 __init__ 执行")
a = A() # 输出 "B 的 __init__ 执行"
print(type(a)) # <class '__main__.B'>
六、 注意事项与常见误区
-
new 必须返回一个实例,否则创建的对象为 None。
-
init 不能有返回值(除了 None),否则会引发 TypeError。
-
如果没有重写 new,默认会调用 object.new。
-
重写 new 时,通常使用 super().new(cls) 来创建实例,避免无限递归。
-
__init__只有在 new 返回本类实例时才会被调用,返回其他类实例时不会调用。
-
对于不可变类型的子类,通常只需要重写 new,因为 init 在不可变对象创建后不会被调用(但实际仍会调用,只是不可变对象在 new 中已经确定了值)。
七、总结
new 是真正的"构造方法",负责创建并返回实例对象,常用于控制实例的生成过程。
init 是"初始化方法",负责设置实例的初始状态,是最常见的魔法方法。
两者在对象创建过程中按顺序执行:new →(如果返回本类实例)→ init。
希望本文能对你有所帮助,欢迎在评论区留言讨论!