Python中__init__与__new__方法的详解与比较


1. __new__ 方法

作用:负责创建对象实例(实际的内存分配)。
调用时机:在对象实例化时最先被调用
特点
• 是一个静态方法(但不需要用 @staticmethod 显式声明)。
• 必须返回一个实例对象(通常是当前类的实例,但可以是其他类的实例)。
• 如果返回的不是实例,__init__ 将不会执行。
语法

def __new__(cls, *args, **kwargs):
    return super().__new__(cls)  # 调用父类的 __new__ 创建实例
使用场景

• 控制实例的创建逻辑(例如单例模式)。
• 继承不可变类型(如 str, tuple)时定制对象创建。
• 动态决定返回哪个类的实例。

示例
class Singleton:
    _instance = None

    def __new__(cls):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

a = Singleton()
b = Singleton()
print(a is b)  # 输出 True(单例)

2. __init__ 方法

作用:负责初始化实例(设置初始状态)。
调用时机:在 __new__ 返回实例后自动调用
特点
• 是一个实例方法,第一个参数是 self(即 __new__ 返回的实例)。
• 不能有返回值(否则会抛出 TypeError)。
语法

def __init__(self, *args, **kwargs):
    self.attribute = value  # 初始化对象属性
使用场景

• 设置对象的初始属性。
• 执行对象创建后的初始化逻辑(如打开文件、连接数据库)。

示例
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(p.name)  # 输出 "Alice"

3. 两者协作流程

当执行 obj = MyClass(args) 时:

  1. __new__ 被调用MyClass.__new__(cls, args) 创建实例。
  2. 实例传递给 __init__:如果 __new__ 返回当前类的实例,__init__ 被调用,接收实例作为 self
  3. 初始化完成:最终返回初始化后的对象。

4. 关键区别

特性 __new__ __init__
角色 创建实例 初始化实例
调用顺序 最先调用 __new__ 之后调用
返回值 必须返回实例 无返回值(None
参数 第一个参数是 cls(类) 第一个参数是 self(实例)
是否必需 默认由 object 提供 可选(不定义则无初始化操作)

5. 高级用法示例

继承不可变类型
class UpperStr(str):
    def __new__(cls, value):
        return super().__new__(cls, value.upper())

s = UpperStr("hello")
print(s)  # 输出 "HELLO"
动态返回不同实例
class BaseClass:
    def __new__(cls, value):
        if value > 0:
            return Positive()
        else:
            return Negative()

class Positive:
    pass

class Negative:
    pass

obj = BaseClass(5)
print(type(obj))  # 输出 <class '__main__.Positive'>

总结

优先使用 __init__:除非你需要控制实例的创建过程,否则无需重写 __new__
谨慎重写 __new__:它会影响对象创建的核心逻辑,容易引入隐蔽的 Bug。
理解协作关系__new____init__ 共同完成对象从创建到初始化的完整流程。

下面再提供几个具体的代码示例,帮助你更深入理解 __new____init__ 的不同应用场景:


示例 1:参数预验证(在 __new__ 中拦截无效参数)

class PositiveInteger:
    def __new__(cls, value):
        # 在创建实例前验证参数
        if not isinstance(value, int) or value <= 0:
            raise ValueError("必须为正整数")
        # 调用父类的 __new__ 创建实例
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        # 此时 value 已经被验证为合法
        self.value = value

# 合法
a = PositiveInteger(5)
print(a.value)  # 输出 5

# 非法(触发 __new__ 中的异常)
try:
    b = PositiveInteger(-3)
except ValueError as e:
    print(e)  # 输出 "必须为正整数"

关键点
__new__ 在实例化前拦截非法参数,避免无效对象进入 __init__ 流程。


示例 2:不可变对象的属性初始化(直接在 __new__ 中设置属性)

class ImmutablePoint:
    def __new__(cls, x, y):
        instance = super().__new__(cls)
        # 直接操作实例的 __dict__ 设置属性(避免触发 __setattr__)
        instance.__dict__['x'] = x
        instance.__dict__['y'] = y
        return instance

    def __setattr__(self, name, value):
        # 禁止修改属性
        raise AttributeError("ImmutablePoint 不可修改")

p = ImmutablePoint(2, 3)
print(p.x, p.y)  # 输出 2 3

try:
    p.x = 5  # 触发错误
except AttributeError as e:
    print(e)  # 输出 "ImmutablePoint 不可修改"

关键点
• 不可变对象通常需要在 __new__ 中初始化属性,因为 __init__ 中无法绕过 __setattr__ 的限制。


示例 3:对象池(复用已有实例)

class DatabaseConnection:
    _pool = []
    _max_size = 2  # 池中最多保存 2 个实例

    def __new__(cls):
        # 如果池中有可用实例,直接复用
        if len(cls._pool) > 0:
            print("复用现有连接")
            return cls._pool.pop()
        # 否则创建新实例
        instance = super().__new__(cls)
        return instance

    def __init__(self):
        # 标记连接是否已初始化
        if not hasattr(self, 'initialized'):
            print("初始化新连接")
            self.initialized = True
            # 模拟连接数据库的操作
            self.id = id(self)
        else:
            print("连接已初始化")

    def close(self):
        # 将实例放回池中
        if len(DatabaseConnection._pool) < DatabaseConnection._max_size:
            DatabaseConnection._pool.append(self)
            print(f"连接 {self.id} 已归还到池中")

# 使用示例
conn1 = DatabaseConnection()  # 输出 "初始化新连接"
conn2 = DatabaseConnection()  # 输出 "初始化新连接"
conn1.close()                 # 输出 "连接 140000000 已归还到池中"
conn2.close()                 # 输出 "连接 140000001 已归还到池中"

conn3 = DatabaseConnection()  # 输出 "复用现有连接"
conn4 = DatabaseConnection()  # 输出 "复用现有连接"
conn3.close()                 # 池已满,无法归还

关键点
• 通过 __new__ 控制实例的创建和复用,减少资源开销。


示例 4:动态生成子类实例(工厂模式)

class Animal:
    def __new__(cls, animal_type, *args):
        # 根据类型返回不同子类的实例
        if animal_type == "dog":
            return Dog(*args)
        elif animal_type == "cat":
            return Cat(*args)
        else:
            raise ValueError("未知动物类型")

class Dog:
    def __init__(self, name):
        self.name = name
        self.sound = "汪汪"

class Cat:
    def __init__(self, name):
        self.name = name
        self.sound = "喵喵"

# 通过父类 Animal 创建不同子类实例
dog = Animal("dog", "小黑")
print(f"{dog.name}: {dog.sound}")  # 输出 "小黑: 汪汪"

cat = Animal("cat", "小白")
print(f"{cat.name}: {cat.sound}")  # 输出 "小白: 喵喵"

关键点
__new__ 可以灵活决定返回哪个类的实例,实现类似工厂模式的效果。


示例 5:记录实例创建日志

class LoggedInstance:
    _instances = 0

    def __new__(cls):
        # 每次创建实例时计数
        cls._instances += 1
        instance = super().__new__(cls)
        instance.id = cls._instances
        return instance

    def __init__(self):
        print(f"实例 {self.id} 已创建")

a = LoggedInstance()  # 输出 "实例 1 已创建"
b = LoggedInstance()  # 输出 "实例 2 已创建"

关键点
• 在 __new__ 中统计实例数量,__init__ 中输出日志。


总结

__new__ 的典型场景:单例、不可变对象、参数验证、对象池、动态实例生成。
__init__ 的典型场景:初始化对象属性、配置依赖关系。
• 优先使用 __init__,仅在需要控制实例创建逻辑时重写 __new__

作者:NurDroid

物联沃分享整理
物联沃-IOTWORD物联网 » Python中__init__与__new__方法的详解与比较

发表回复