魔法方法 __init__ 与 __new__ 的区别与使用场景

前言

在 Python 中,魔法方法(也叫特殊方法)以双下划线开头和结尾,例如 initnewstr 等。它们赋予了类许多"隐形"的能力,让我们能够像操作内置类型一样操作自定义对象。

当谈到对象创建时,newinit 是两个最容易混淆的魔法方法。很多人以为 init 就是构造函数,其实严格来说,new 才是真正的"构造"方法,而 init 是"初始化"方法。

本文将深入剖析 newinit 的区别、调用顺序、参数以及各自的使用场景,通过代码示例,帮助你彻底掌握它们。

一、 对象创建的过程

在 Python 中,当我们执行 obj = MyClass(arg1, arg2) 时,解释器内部经历了以下两个步骤:

  1. 调用 new:分配内存,创建一个新的实例对象(通常返回该实例)。

  2. 调用 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 不会执行。

四、newinit 的区别对比

|--------|-------------------------|------------------------|
| 特性 | 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'>

六、 注意事项与常见误区

  1. new 必须返回一个实例,否则创建的对象为 None。

  2. init 不能有返回值(除了 None),否则会引发 TypeError。

  3. 如果没有重写 new,默认会调用 object.new

  4. 重写 new 时,通常使用 super().new(cls) 来创建实例,避免无限递归。

  5. __init__只有在 new 返回本类实例时才会被调用,返回其他类实例时不会调用。

  6. 对于不可变类型的子类,通常只需要重写 new,因为 init 在不可变对象创建后不会被调用(但实际仍会调用,只是不可变对象在 new 中已经确定了值)。

七、总结

new 是真正的"构造方法",负责创建并返回实例对象,常用于控制实例的生成过程。

init 是"初始化方法",负责设置实例的初始状态,是最常见的魔法方法。

两者在对象创建过程中按顺序执行:new →(如果返回本类实例)→ init

希望本文能对你有所帮助,欢迎在评论区留言讨论!

相关推荐
傻啦嘿哟2 小时前
如何使用 Python 操作 Excel 图片:插入、提取与压缩
开发语言·python·excel
Dxy12393102162 小时前
Python 如何反向 `enumerate` 遍历枚举
python
程序员杰哥2 小时前
软件测试之黑盒测试详解
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
第一程序员2 小时前
Python深度学习实战:从理论到应用
python·github
乐园游梦记2 小时前
下载 Docker 镜像(CVAT)资源
人工智能·python·深度学习·yolo·机器学习·cvat
第一程序员2 小时前
Python数据分析:Pandas vs Polars 详细对比
python·github
程序员杰哥2 小时前
Web UI自动化测试之PO篇
自动化测试·软件测试·python·selenium·测试工具·ui·测试用例
无责任此方_修行中2 小时前
MkDocs宫斗事件:开源项目控制权争夺与技术社区分裂
python·程序员
lagrahhn2 小时前
python面向对象中__new__和__init__区别
后端·python·程序员