【Python】 __new__

Python 魔术方法 __new__
序章:对象世界的开端——__new__ 的根本地位
在 Python 的面向对象编程世界中,每一个实例的诞生都并非凭空而生,其背后蕴藏着一套严谨而精妙的机制。我们通常熟悉的是 __init__ 方法,它负责初始化一个已经存在的对象。然而,在 __init__ 被调用之前,到底是什么力量在默默地构建着这个对象的骨架?答案便是:__new__ 魔术方法。
__new__ 方法是 Python 中最神秘也最强大的魔术方法之一,因为它直接干预了对象的创建过程。它并非简单的初始化器,而是真正的“构造器”——一个在对象被实例化之前被调用的工厂函数,负责在内存中分配空间并返回一个新的对象实例。理解 __new__,就是理解 Python 对象模型的核心,掌握了它,你就掌握了在最底层控制对象生命周期的能力,从而解锁更多高级、灵活的设计模式。
本章将作为我们深度探索的起点,我们将首先建立对 __new__ 的宏观认识,辨析它与 __init__ 的本质区别,并逐步揭示其在 Python 对象创建流程中的不可或缺的地位。
1.1 Python 对象创建的基石:一个宏观视角
要理解 __new__,我们首先需要跳出日常的编码习惯,从更宏观的角度审视 Python 中一个对象的诞生过程。当我们执行 MyClass(*args, **kwargs) 这样的代码时,背后究竟发生了什么?
通常的认知是:
调用 MyClass。
__init__ 被调用,初始化对象。
返回一个对象实例。
这个认知在大多数情况下是足够用的,但它省略了最关键的第一步:对象的“生产”过程。实际上,Python 对象的创建分为两个截然不同的阶段:
阶段一:对象的创建 (Construction):在这个阶段,系统需要为即将诞生的对象在内存中分配一块区域,并根据类的定义,构建一个“空壳”对象。这个“空壳”对象是一个真实存在的实例,但其内部状态(属性值)尚未被填充。这一阶段,正是 __new__ 方法发挥作用的舞台。
阶段二:对象的初始化 (Initialization):在“空壳”对象创建完成后,系统会将其作为参数传递给 __init__ 方法。__init__ 的任务就是接收这个已经存在的对象,并根据传入的参数,填充其内部状态,设定初始属性值,使其成为一个完整的、可用的对象。
用一个形象的比喻来说: 如果你要建造一栋房子:
__new__ 就像是建筑工人,他们负责打地基、砌墙、盖屋顶,构建出房子的主体结构。这栋房子已经存在了,但里面空空如也,没有家具,没有装修。
__init__ 就像是室内设计师和装修工人,他们负责在房子主体结构完成后,进行内部装修、摆放家具、连接水电等,让这栋房子变得宜居。
因此,__new__ 关注的是**“如何创建这个对象”,而 __init__ 关注的是“如何初始化这个已经创建的对象”**。这种职责的明确划分,为 Python 对象的灵活创建和定制提供了强大的基础。
1.2 __new__ 与 __init__:职责的清晰界限
理解 __new__ 与 __init__ 的核心区别是掌握 __new__ 的关键。虽然它们都与对象的生命周期相关,但其目的和时机截然不同。
1.2.1 __init__ 的本质:初始化器
我们首先回顾 __init__:
调用时机:在一个对象已经被创建(即内存已分配,骨架已搭建)之后,__init__ 立即被调用。
目的:接收已经存在的对象实例(通常命名为 self),并对该实例的属性进行赋值,设置其初始状态。它不负责创建对象本身。
返回值:__init__ 方法不应该有明确的返回值。如果它返回了任何非 None 的值,Python 会引发 TypeError。这是因为它被设计为一个“就地修改”操作,而不是一个“生产”操作。
参数:第一个参数约定为 self,指向即将被初始化的对象实例。后续参数是用户在实例化类时传递的参数。
示例代码:
class MyBasicObject:
def __init__(self, value): # 定义 __init__ 方法,用于初始化对象
# self 参数是已经被 __new__ 创建好的对象实例
self.data = value # 将传入的 value 赋值给对象的 data 属性,完成初始化
print(f"MyBasicObject 实例已初始化,data 为: {
self.data}") # 打印初始化信息
# 创建一个 MyBasicObject 实例
obj = MyBasicObject(100) # 当调用 MyBasicObject(100) 时,先是 MyBasicObject.__new__ 创建对象,然后 MyBasicObject.__init__ 被调用
# 访问对象的属性
print(f"对象 obj 的 data 属性是: {
obj.data}") # 打印对象 obj 的 data 属性值
代码解释:
class MyBasicObject::定义一个名为 MyBasicObject 的类。
def __init__(self, value)::定义了类的初始化方法 __init__。这个方法会在对象被创建后自动调用。
self.data = value:在 __init__ 方法内部,将传入的 value 参数赋值给当前对象的 data 属性。self 代表当前正在被初始化的对象实例。
print(f"MyBasicObject 实例已初始化,data 为: {self.data}"):打印一条消息,表明对象已初始化,并显示其 data 属性的值。
obj = MyBasicObject(100):创建 MyBasicObject 类的一个实例,并将 100 作为 value 参数传递给 __init__ 方法。
print(f"对象 obj 的 data 属性是: {obj.data}"):访问并打印 obj 实例的 data 属性。
1.2.2 __new__ 的本质:构造器/创建器
现在,让我们聚焦于 __new__:
调用时机:__new__ 方法在 __init__ 之前被调用,它是类实例化过程中第一个被调用的方法。它的任务是实际地创建并返回一个新对象。
目的:在内存中分配空间,创建并返回一个新的实例对象。它负责对象的诞生。
返回值:__new__ 方法必须返回一个对象实例,通常是其所定义类的实例,或者其子类的实例。如果 __new__ 没有返回一个新对象实例,那么 __init__ 将不会被调用,并且整个实例化过程将不会产生一个有效的对象。
参数:第一个参数约定为 cls,代表当前正在被实例化的类本身(而不是一个实例)。后续参数 *args 和 **kwargs 是用户在实例化类时传递的参数,这些参数会原封不动地传递给 __new__。
默认情况下,如果我们没有在类中显式定义 __new__ 方法,Python 会自动查找其基类(通常最终会是 object 类)的 __new__ 方法来完成对象的创建。object 类的 __new__ 方法是所有 Python 对象的创建起点。
示例代码(引入 __new__ 的基本用法):
class MyComplexObject:
def __new__(cls, *args, **kwargs): # 定义 __new__ 方法,它的第一个参数是类本身 (cls)
print(f"--- __new__ 方法被调用,类是: {
cls.__name__} ---") # 打印 __new__ 被调用的信息
# 这是调用基类(通常是 object)的 __new__ 方法来创建实际的对象实例
# 这一步是关键,它负责在内存中分配空间并返回一个新的对象
instance = super().__new__(cls) # 调用父类的 __new__ 方法来创建实例,通常是 object.__new__(cls)
print(f"--- 对象 {
instance} 已由 __new__ 创建 ---") # 打印新创建的实例信息
# 可以对 instance 进行一些在 __init__ 之前进行的操作,但通常不在此处进行复杂初始化
return instance # __new__ 必须返回一个对象实例,这个实例会被传递给 __init__
def __init__(self, value): # 定义 __init__ 方法,用于初始化对象
print(f"--- __init__ 方法被调用,初始化对象: {
self} ---") # 打印 __init__ 被调用的信息
self.initialized_value = value # 对对象的属性进行初始化
print(f"--- 对象 {
self} 的 initialized_value 属性被设置为: {
self.initialized_value} ---") # 打印属性设置信息
# 创建 MyComplexObject 的实例
print("\n开始创建 MyComplexObject 实例:")
obj_complex = MyComplexObject(200) # 当调用 MyComplexObject(200) 时,__new__ 先被调用,然后 __init__ 被调用
print(f"最终得到的对象是: {
obj_complex}") # 打印最终创建的对象
print(f"对象 obj_complex 的 initialized_value 属性是: {
obj_complex.initialized_value}") # 访问并打印属性
代码解释:
class MyComplexObject::定义一个名为 MyComplexObject 的类。
def __new__(cls, *args, **kwargs)::定义了类的 __new__ 方法。
cls:代表 MyComplexObject 这个类本身。
*args, **kwargs:捕获所有位置参数和关键字参数。
print(f"--- __new__ 方法被调用,类是: {cls.__name__} ---"):打印一条消息,指示 __new__ 方法被调用,并显示当前正在创建实例的类名。
instance = super().__new__(cls):这是 __new__ 方法中最核心的一行。它调用了当前类的父类(在这里是 object)的 __new__ 方法来实际创建一个新的对象实例。super().__new__(cls) 会返回一个新分配的、尚未初始化的 MyComplexObject 实例。
print(f"--- 对象 {instance} 已由 __new__ 创建 ---"):打印新创建的实例的内存地址,表明对象已被 __new__ 方法创建。
return instance:__new__ 方法必须返回一个对象实例。这个返回的对象就是后续 __init__ 方法的 self 参数。
def __init__(self, value)::定义了 MyComplexObject 类的 __init__ 方法。
print(f"--- __init__ 方法被调用,初始化对象: {self} ---"):打印一条消息,指示 __init__ 被调用,并显示正在初始化的对象。
self.initialized_value = value:将 __init__ 接收到的 value 参数赋值给对象的 initialized_value 属性。
print(f"--- 对象 {self} 的 initialized_value 属性被设置为: {self.initialized_value} ---"):打印属性设置完成的信息。
obj_complex = MyComplexObject(200):创建 MyComplexObject 的一个实例,传入 200。您可以看到输出中 __new__ 先于 __init__ 被调用。
1.2.3 核心差异总结表格
特性
__new__
__init__
目的
创建对象实例(在内存中分配并返回一个新对象)
初始化对象实例(设置已存在对象的属性和状态)
调用时机
在 __init__ 之前被调用
在 __new__ 返回对象后,立即被调用
第一个参数
cls (代表类本身)
self (代表已经创建的对象实例)
返回值
必须返回一个对象实例(通常是 cls 的实例)
不能有明确返回值(隐式返回 None)
用途
控制对象的创建过程,例如实现单例模式、不可变对象、元类高级用法
初始化对象状态,设置属性值
这个表格清晰地展现了两者在 Python 对象生命周期中的不同角色。__new__ 是“创造者”,__init__ 是“装饰者”。
1.3 object.__new__:所有对象的起源
在 Python 中,所有的类都直接或间接地继承自 object。这意味着,即使我们没有为自己的类定义 __new__ 方法,Python 也会向上查找继承链,最终调用到 object 类中实现的 __new__ 方法。
object.__new__(cls) 是一个内置函数,它执行了以下核心操作:
内存分配:在系统内存中为 cls 类的一个新实例分配足够的空间。
基本初始化:对这块新分配的内存进行一些最低限度的初始化,使其成为一个有效的 Python 对象骨架。这个骨架通常只包含一些元数据(如指向其类型的指针)。
返回实例:返回这个新创建并初始化的对象实例。
正是 object.__new__(cls) 奠定了所有 Python 对象的基石。当我们自定义 __new__ 方法时,通常会像前面示例中那样,通过 super().__new__(cls) 调用它,以便利用 Python 内置的、高效的内存分配和对象骨架创建机制。
如果你在 __new__ 中不调用 super().__new__(cls) 并尝试返回一个完全自定义的“对象”,那么这个“对象”可能不会被 Python 的对象模型正确识别,并且 __init__ 也不会被调用。
示例:不调用 super().__new__ 的风险
class RiskyNew:
def __new__(cls, *args, **kwargs): # 定义 __new__ 方法
print(f"--- RiskyNew.__new__ 被调用 ---") # 打印信息
# 错误示例:不返回通过 super() 创建的实例
# 而是尝试返回一个普通的 Python 字典
# 这样的字典不是 RiskyNew 类的实例
print("警告:__new__ 返回一个非预期类型的对象,将跳过 __init__") # 打印警告
return {
"custom_data": args[0] if args else "default"} # __new__ 返回一个字典,而不是 RiskyNew 的实例
def __init__(self, value): # 定义 __init__ 方法
# 这个 __init__ 方法将永远不会被调用,因为 __new__ 没有返回 RiskyNew 的实例
print(f"--- RiskyNew.__init__ 被调用,但这永远不会发生 ---") # 打印信息,但实际不会执行
self.value = value # 尝试设置属性
print("\n尝试创建 RiskyNew 实例 (__new__ 返回字典):")
risky_obj = RiskyNew(300) # 调用 RiskyNew(300)
print(f"risky_obj 的类型是: {
type(risky_obj)}") # 打印 risky_obj 的类型
print(f"risky_obj 的内容是: {
risky_obj}") # 打印 risky_obj 的内容
# 尝试访问属性,将导致 AttributeError,因为返回的是字典,而不是类的实例
# print(risky_obj.value) # 这一行会报错,因为 risky_obj 不是 RiskyNew 的实例
代码解释:
class RiskyNew::定义一个名为 RiskyNew 的类。
def __new__(cls, *args, **kwargs)::定义了 __new__ 方法。
return {"custom_data": args[0] if args else "default"}:关键在于这里,__new__ 方法返回了一个普通的 Python 字典,而不是通过 super().__new__(cls) 创建的 RiskyNew 实例。
def __init__(self, value)::定义了 __init__ 方法。
当执行 risky_obj = RiskyNew(300) 时:
RiskyNew.__new__ 被调用,并返回了那个字典。
因为 __new__ 返回的不是 RiskyNew 类的实例,Python 会跳过对 RiskyNew.__init__ 的调用。
因此,risky_obj 实际上是一个字典,而不是 RiskyNew 的实例。
print(f"risky_obj 的类型是: {type(risky_obj)}"):输出显示 risky_obj 的类型是
print(f"risky_obj 的内容是: {risky_obj}"):输出字典的内容。
注释掉的 print(risky_obj.value):如果取消注释,这将引发 AttributeError: 'dict' object has no attribute 'value',因为字典没有名为 value 的属性,而且 __init__ 也没有被调用来设置它。
这个例子明确地展示了 __new__ 的返回值的极端重要性:它决定了 __init__ 是否被调用以及被初始化的是哪种对象。通常情况下,__new__ 应该返回当前类的实例或其子类的实例。
1.4 为什么需要 __new__?场景初探
既然 __new__ 如此底层和“危险”(如果使用不当),为什么 Python 会提供它呢?它的存在是为了解决那些仅仅通过 __init__ 无法解决的问题,主要涉及以下几个核心场景:
控制对象的创建过程本身:这是 __new__ 最直接的用途。如果你需要在对象实例被创建之前,甚至在分配内存之前,就干预这个过程,那么 __new__ 是唯一的选择。
实现单例模式 (Singleton Pattern):单例模式确保一个类只有一个实例。这需要我们在对象创建的早期阶段就进行检查,如果实例已经存在,就返回现有实例,而不是创建新的。__new__ 正是实现这一逻辑的完美场所。
创建不可变对象 (Immutable Objects):一旦对象被创建,其状态就不能再改变。对于一些复杂不可变对象的构造,__new__ 可以确保在对象完全形成之前,其所有组成部分都被正确设置,且无法在 __init__ 之后被修改(因为 __init__ 可以修改 self)。
自定义类的实例化行为:当你需要一个类在某些条件下返回不同类型的实例,或者根据参数决定是否创建新实例时,__new__ 提供了这种灵活性。
元类编程 (Metaclass Programming):当你需要控制类的创建过程时(是的,类也是对象,它们也是被创建出来的),元类的 __new__ 方法扮演着至关重要的角色。这是 __new__ 最复杂也最强大的应用之一。
实现某些设计模式,如工厂模式的变体:__new__ 可以被视为一种特殊的工厂方法,它直接在类定义内部控制了实例的生产。
在后续章节中,我们将对这些场景进行逐一的、极度深入的剖析,并辅以大量原创且详尽的代码示例。
第二章:__new__ 的基本机制与参数解析
在本章中,我们将深入探讨 __new__ 方法的签名、参数以及它如何与 Python 的类型系统协同工作。理解这些基本机制是掌握 __new__ 高级应用的先决条件。我们将详细分析 cls 参数的意义,*args 和 **kwargs 的作用,以及 __new__ 的返回值如何影响整个对象创建流程。
2.1 __new__ 方法的签名:cls, *args, **kwargs
__new__ 方法的典型签名是 __new__(cls, *args, **kwargs)。让我们逐一解构这些参数:
2.1.1 cls 参数:类本身的引用
与 __init__ 方法的第一个参数 self(代表实例)不同,__new__ 方法的第一个参数是 cls(约定俗成,也可以是其他名称,但 cls 最常见)。这个 cls 参数代表了当前正在被实例化的类本身。
当我们执行 MyClass(arg1, arg2) 时,Python 内部实际上会首先调用 MyClass.__new__(MyClass, arg1, arg2)。这里的第一个参数 MyClass 就是传递给 __new__ 的 cls。
为什么是 cls 而不是 self? 因为在 __new__ 被调用时,对象实例 self 还没有被创建!__new__ 的核心任务就是创建这个 self。因此,它需要知道是哪个类发出了创建实例的请求,以便正确地分配内存和构建对应类型的对象。cls 参数正是提供了这个上下文。
示例:观察 cls 参数
class ClassObserver:
def __new__(cls, *args, **kwargs): # __new__ 方法的第一个参数是 cls
print(f"\n--- 在 __new__ 方法中 ---") # 打印进入 __new__ 的信息
print(f"cls 参数的值是: {
cls}") # 打印 cls 参数的原始值
print(f"cls 参数的类型是: {
type(cls)}") # 打印 cls 参数的类型
print(f"cls.__name__ 是: {
cls.__name__}") # 打印 cls 的名字
print(f"cls.__bases__ 是: {
cls.__bases__}") # 打印 cls 的基类
# 始终通过 super() 调用基类的 __new__ 来创建实际的对象实例
instance = super().__new__(cls) # 调用父类的 __new__ 来创建实例
print(f"已创建的实例 (由 super().__new__ 返回) 是: {
instance}") # 打印创建的实例
return instance # 返回这个实例,以便 __init__ 可以初始化它
def __init__(self, name): # __init__ 方法的第一个参数是 self
print(f"\n--- 在 __init__ 方法中 ---") # 打印进入 __init__ 的信息
print(f"self 参数的值是: {
self}") # 打印 self 参数的原始值
print(f"self 参数的类型是: {
type(self)}") # 打印 self 参数的类型
print(f"self.__class__.__name__ 是: {
self.__class__.__name__}") # 打印 self 的类名
self.name = name # 初始化实例的 name 属性
print("创建 ClassObserver 实例 'obj1':")
obj1 = ClassObserver("第一个实例") # 调用 ClassObserver 创建实例
print(f"最终实例 obj1 的 name 是: {
obj1.name}") # 打印实例的 name 属性
# 尝试从另一个类继承并观察
class SubClassObserver(ClassObserver):
pass # 子类不做任何额外操作,继承父类的 __new__ 和 __init__
print("\n创建 SubClassObserver 实例 'obj_sub':")
obj_sub = SubClassObserver("子类实例") # 调用 SubClassObserver 创建实例
print(f"最终实例 obj_sub 的 name 是: {
obj_sub.name}") # 打印实例的 name 属性
代码解释:
class ClassObserver::定义一个名为 ClassObserver 的类。
def __new__(cls, *args, **kwargs)::在 __new__ 方法中,我们打印了 cls 参数的各种信息。
cls:将打印出
type(cls):将是
cls.__name__:将是 'ClassObserver' 或 'SubClassObserver'。
cls.__bases__:显示类的基类元组。
instance = super().__new__(cls):在这里,super().__new__(cls) 确保了创建的实例是 cls 指定的类型。
def __init__(self, name)::在 __init__ 方法中,我们打印了 self 参数的各种信息。
self:将打印出类似 <__main__.ClassObserver object at 0x...> 的实例地址。
type(self):将是
self.__class__.__name__:也将是 'ClassObserver' 或 'SubClassObserver'。
当创建 obj1 = ClassObserver("第一个实例") 时,ClassObserver.__new__ 会被调用,cls 就是 ClassObserver。
当创建 obj_sub = SubClassObserver("子类实例") 时,由于 SubClassObserver 没有自己的 __new__,它会继承并调用 ClassObserver 的 __new__。此时,传入 ClassObserver.__new__ 的 cls 参数将是 SubClassObserver,而不是 ClassObserver。这体现了 __new__ 在处理继承时的重要特性:它知道实际被请求创建的是哪个子类的实例。
这个例子清楚地表明,cls 是一个类对象,它指导着 __new__ 方法去创建正确类型的实例。
2.1.2 *args 和 **kwargs:透传参数
__new__ 方法接收的 *args 和 **kwargs 与函数中常见的可变位置参数和关键字参数收集方式完全一致。它们的作用是:捕获所有在实例化类时传递给构造器的额外参数,并将它们原封不动地传递给 __new__ 和随后的 __init__。
当你调用 MyClass(param1, param2, key1=val1) 时:
param1, param2 会被收集到 *args 元组中。
key1=val1 会被收集到 **kwargs 字典中。
重要的是,这些参数在 __new__ 和 __init__ 之间是“透传”的。__new__ 方法在创建实例时可以利用这些参数,然后将它们传递给 __init__ 进行初始化。
示例:参数的透传
class ParameterPasser:
def __new__(cls, *args, **kwargs): # __new__ 接收所有参数
print(f"\n--- 在 ParameterPasser.__new__ 方法中 ---") # 打印信息
print(f"接收到的位置参数 (args): {
args}") # 打印 args
print(f"接收到的关键字参数 (kwargs): {
kwargs}") # 打印 kwargs
# 通常,你会将这些参数传递给基类的 __new__
instance = super().__new__(cls) # 调用父类 __new__ 创建实例
# 你也可以在 __new__ 中基于这些参数做一些预处理或检查
if "special_flag" in kwargs and kwargs["special_flag"]: # 检查是否存在 'special_flag'
print("__new__ 检测到特殊标记,可能进行特殊处理...") # 打印特殊处理信息
instance._special_attr_from_new = "New-Special" # 在 __new__ 中设置一个特殊属性
return instance # 返回实例
def __init__(self, param_a, param_b, *args_init, **kwargs_init): # __init__ 也接收所有参数
print(f"\n--- 在 ParameterPasser.__init__ 方法中 ---") # 打印信息
self.param_a = param_a # 初始化 param_a
self.param_b = param_b # 初始化 param_b
print(f"__init__ 初始化 param_a={
self.param_a}, param_b={
self.param_b}") # 打印初始化信息
print(f"__init__ 接收到的额外位置参数 (args_init): {
args_init}") # 打印 args_init
print(f"__init__ 接收到的额外关键字参数 (kwargs_init): {
kwargs_init}") # 打印 kwargs_init
# 实例化类,传递不同类型的参数
print("创建实例 p1:")
p1 = ParameterPasser(10, 20, "extra_arg1", name="Alice", age=30) # 传递多种参数
print(f"\np1.param_a: {
p1.param_a}") # 访问 p1 的 param_a 属性
print(f"p1.param_b: {
p1.param_b}") # 访问 p1 的 param_b 属性
# 尝试访问 __new__ 中设置的特殊属性 (如果不存在会报错)
if hasattr(p1, '_special_attr_from_new'): # 检查 p1 是否有 _special_attr_from_new 属性
print(f"p1._special_attr_from_new: {
p1._special_attr_from_new}") # 打印属性值
else:
print("p1 没有 _special_attr_from_new 属性.") # 打印没有属性的信息
print("\n创建实例 p2 (带特殊标记):")
p2 = ParameterPasser(1, 2, special_flag=True, data="important") # 传递带特殊标记的参数
print(f"\np2.param_a: {
p2.param_a}") # 访问 p2 的 param_a 属性
print(f"p2.param_b: {
p2.param_b}") # 访问 p2 的 param_b 属性
if hasattr(p2, '_special_attr_from_new'): # 检查 p2 是否有 _special_attr_from_new 属性
print(f"p2._special_attr_from_new: {
p2._special_attr_from_new}") # 打印属性值
else:
print("p2 没有 _special_attr_from_new 属性.") # 打印没有属性的信息
代码解释:
class ParameterPasser::定义一个名为 ParameterPasser 的类。
def __new__(cls, *args, **kwargs)::__new__ 方法接收了所有传递给类构造器的参数,并分别打印了 args 元组和 kwargs 字典。
if "special_flag" in kwargs and kwargs["special_flag"]::演示了在 __new__ 中根据传入参数进行条件判断的能力。如果 special_flag 存在且为真,则在对象创建时就设置一个特殊属性 _special_attr_from_new。
instance = super().__new__(cls):创建实际的对象实例。
def __init__(self, param_a, param_b, *args_init, **kwargs_init)::__init__ 方法也接收了所有参数。注意到它明确列出了 param_a 和 param_b 作为普通参数,这说明 __init__ 可以有自己的明确参数列表,也可以像 __new__ 一样使用 *args_init 和 **kwargs_init 来捕获剩余参数。
p1 = ParameterPasser(10, 20, "extra_arg1", name="Alice", age=30):
10, 20 会匹配 __init__ 的 param_a 和 param_b。
"extra_arg1" 会进入 args_init。
name="Alice", age=30 会进入 kwargs_init。
在 __new__ 中,args 会是 (10, 20, 'extra_arg1'),kwargs 会是 {'name': 'Alice', 'age': 30}。
p2 = ParameterPasser(1, 2, special_flag=True, data="important"):
__new__ 会看到 special_flag=True,因此会设置 _special_attr_from_new。
__init__ 会处理 1, 2 作为 param_a, param_b,并处理 data="important" 作为 kwargs_init。
这个例子强调了 __new__ 和 __init__ 都能够接收实例化时传入的所有参数。在 __new__ 中,你可以根据这些参数决定创建何种对象,甚至返回一个现有对象;而在 __init__ 中,你则用这些参数来填充对象的初始状态。
2.2 __new__ 的返回值:实例的命运
__new__ 方法的返回值是其最关键的特性之一。它的返回值直接决定了后续 __init__ 方法是否会被调用,以及哪个对象实例会被初始化。
核心规则:
必须返回一个对象实例:__new__ 应该总是返回一个对象实例。如果它返回 None 或没有明确返回任何值(隐式返回 None),Python 将不会调用 __init__,并且类实例化操作将不会产生任何对象。
返回的实例类型与 cls 的关系:
通常情况:__new__ 返回的实例是 cls 指定的类型,或者 cls 的子类型。在这种情况下,Python 会自动调用这个返回实例的 __init__ 方法(如果该方法存在)。例如,如果 MyClass.__new__ 返回了一个 MyClass 的实例,那么 MyClass.__init__ 就会被调用。
特殊情况:如果 __new__ 返回的实例不是 cls 指定的类型,也不是 cls 的子类型,那么 Python 将不会调用任何 __init__ 方法。它会直接返回 __new__ 所返回的对象。这是实现某些高级模式(如工厂模式,或返回现有对象)的关键。
2.2.1 正常返回 cls 实例
这是最常见的情况,也是 super().__new__(cls) 的典型行为。
class NormalCreator:
def __new__(cls, value): # __new__ 接收 value
print(f"\n--- NormalCreator.__new__ 被调用,值为: {
value} ---") # 打印信息
instance = super().__new__(cls) # 通过父类 __new__ 创建实例
# 可以在此处基于 value 对 instance 进行一些预设置,但通常交给 __init__
instance._creation_tag = f"Created with {
value}" # 在 __new__ 中设置一个属性
print(f"--- NormalCreator.__new__ 返回实例: {
instance} ---") # 打印返回的实例
return instance # 返回实例,这将触发 __init__
def __init__(self, value): # __init__ 接收 value
print(f"--- NormalCreator.__init__ 被调用,初始化实例: {
self} 值为: {
value} ---") # 打印信息
self.data = value # 初始化实例的 data 属性
print(f"--- NormalCreator.__init__ 完成初始化 ---") # 打印完成信息
print("创建 NormalCreator 实例 'n1':")
n1 = NormalCreator(100) # 创建实例
print(f"n1.data: {
n1.data}") # 访问 data 属性
print(f"n1._creation_tag: {
n1._creation_tag}") # 访问 _creation_tag 属性
print(f"n1 的类型: {
type(n1)}") # 打印 n1 的类型
代码解释:
class NormalCreator::定义一个名为 NormalCreator 的类。
def __new__(cls, value)::__new__ 方法内部:
instance = super().__new__(cls):创建了一个 NormalCreator 的实例。
instance._creation_tag = f"Created with {value}":在 __new__ 中给新创建的实例设置了一个属性。
return instance:返回这个 NormalCreator 实例。
def __init__(self, value)::__init__ 方法被调用,因为它接收到的是一个 NormalCreator 的实例。它继续初始化了 data 属性。
输出表明 __new__ 先被调用,然后 __init__ 被调用,并且两个方法都能访问到这个实例。
2.2.2 返回非 cls 类型的实例:跳过 __init__
这是 __new__ 强大能力的一个体现。当你需要根据条件返回一个完全不同类型的对象时,__new__ 可以胜任。在这种情况下,Python 会认为你已经完成了对象的“创建”工作,并且不需要再进行“初始化”,因此会跳过 __init__。
示例:返回不同类型的对象
class SpecialObjectCreator:
def __new__(cls, type_name, value): # __new__ 接收 type_name 和 value
print(f"\n--- SpecialObjectCreator.__new__ 被调用,type_name: {
type_name}, value: {
value} ---") # 打印信息
if type_name == "dict": # 如果 type_name 是 "dict"
print("--- __new__ 将返回一个字典实例 ---") # 打印信息
return {
"special_key": value} # 返回一个字典
elif type_name == "list": # 如果 type_name 是 "list"
print("--- __new__ 将返回一个列表实例 ---") # 打印信息
return [value, value * 2] # 返回一个列表
else: # 否则,返回 SpecialObjectCreator 自己的实例
print("--- __new__ 将返回 SpecialObjectCreator 实例 ---") # 打印信息
instance = super().__new__(cls) # 创建 SpecialObjectCreator 实例
return instance # 返回实例
def __init__(self, type_name, value): # __init__ 接收 type_name 和 value
# 这个 __init__ 只有在 __new__ 返回 SpecialObjectCreator 实例时才会被调用
print(f"--- SpecialObjectCreator.__init__ 被调用,初始化实例: {
self} ---") # 打印信息
self.type_requested = type_name # 初始化属性
self.initial_value = value # 初始化属性
print(f"--- SpecialObjectCreator.__init__ 完成初始化 ---") # 打印完成信息
print("情景1: 创建 SpecialObjectCreator 实例 (默认行为):")
obj_normal = SpecialObjectCreator("default", 123) # 创建默认实例
print(f"obj_normal 的类型: {
type(obj_normal)}") # 打印类型
print(f"obj_normal.type_requested: {
obj_normal.type_requested}") # 访问属性
print(f"obj_normal.initial_value: {
obj_normal.initial_value}") # 访问属性
print("\n情景2: 创建 SpecialObjectCreator 但返回字典:")
obj_dict = SpecialObjectCreator("dict", {
"a": 1, "b": 2}) # 创建返回字典的实例
print(f"obj_dict 的类型: {
type(obj_dict)}") # 打印类型
print(f"obj_dict 的内容: {
obj_dict}") # 打印内容
# 注意:此时 obj_dict 没有 type_requested 和 initial_value 属性,因为 __init__ 未被调用
print("\n情景3: 创建 SpecialObjectCreator 但返回列表:")
obj_list = SpecialObjectCreator("list", 5) # 创建返回列表的实例
print(f"obj_list 的类型: {
type(obj_list)}") # 打印类型
print(f"obj_list 的内容: {
obj_list}") # 打印内容
代码解释:
class SpecialObjectCreator::定义一个名为 SpecialObjectCreator 的类。
def __new__(cls, type_name, value)::__new__ 方法根据 type_name 参数的值,决定返回不同类型的对象。
if type_name == "dict": return {"special_key": value}:如果 type_name 是 "dict",则 __new__ 直接返回一个字典。
elif type_name == "list": return [value, value * 2]:如果 type_name 是 "list",则 __new__ 直接返回一个列表。
else: instance = super().__new__(cls); return instance:对于其他 type_name,则正常创建并返回 SpecialObjectCreator 类的实例。
def __init__(self, type_name, value)::__init__ 方法被定义了,但它的执行依赖于 __new__ 的返回值。
情景1:obj_normal = SpecialObjectCreator("default", 123)。__new__ 返回了 SpecialObjectCreator 的实例,所以 __init__ 被调用,并成功初始化了 type_requested 和 initial_value 属性。
情景2:obj_dict = SpecialObjectCreator("dict", {"a": 1, "b": 2})。__new__ 返回了一个字典。此时,Python 检测到返回的不是 SpecialObjectCreator 的实例,因此跳过了 SpecialObjectCreator.__init__ 的调用。obj_dict 最终就是一个普通的字典。
情景3:obj_list = SpecialObjectCreator("list", 5)。同理,__new__ 返回了一个列表。SpecialObjectCreator.__init__ 也被跳过。obj_list 最终就是一个普通的列表。
这个例子生动地展示了 __new__ 如何通过返回不同类型的对象来完全控制实例化过程,并且在这种情况下,__init__ 不会再被触发。这对于实现某些高级工厂模式或对象复用策略非常有用。
2.3 super().__new__(cls) 的重要性
在自定义 __new__ 方法时,几乎总是需要调用 super().__new__(cls)(或直接 object.__new__(cls),如果你的类直接继承自 object)。
为什么 super().__new__(cls) 如此重要?
内存分配和基本对象构建:super().__new__(cls) 是 Python 提供给我们的标准机制,用于在内存中为新对象分配空间并进行最小限度的初始化。它高效且正确地完成了作为 Python 对象所必需的底层准备工作。
保证 __init__ 的调用:如前所述,只有当 __new__ 返回一个 cls 类型或其子类型的实例时,__init__ 才会被调用。super().__new__(cls) 恰恰返回了这样一个合法的实例,确保了后续的初始化流程能够顺利进行。
继承链的正确行为:在复杂的继承结构中,super().__new__(cls) 确保了父类或祖先类的 __new__ 逻辑能够被正确地执行,从而构建出一个符合整个继承链期望的对象。它遵循方法解析顺序 (MRO),找到下一个合适的 __new__ 方法。
何时可以不调用 super().__new__(cls)?
极少数情况下,你可能不想调用 super().__new__(cls):
当你明确地想要返回一个完全不同类型的对象时:例如,在上述 SpecialObjectCreator 例子中,当返回字典或列表时,我们就不调用 super().__new__(cls),因为我们根本不希望创建一个 SpecialObjectCreator 的实例。
当你实现一个“元工厂”或代理,且实际的对象创建逻辑完全由你掌控并委托给其他非标准方式时:这通常发生在非常高级和定制化的场景中,比如构建一个ORM层,你可能需要从数据库中“恢复”一个对象,而不是从零开始创建。但即使在这种情况下,最终也往往会间接调用到某种形式的 __new__ 或类似的内存分配机制。
示例:super().__new__(cls) 在继承中的作用
class BaseEntity:
def __new__(cls, *args, **kwargs): # 基类的 __new__
print(f"--- BaseEntity.__new__ 被调用 (cls: {
cls.__name__}) ---") # 打印信息
instance = super().__new__(cls) # 调用 object.__new__
instance._base_creation_info = "Base class created me" # 在基类中添加属性
return instance # 返回实例
def __init__(self, id_val): # 基类的 __init__
print(f"--- BaseEntity.__init__ 被调用 (id: {
id_val}) ---") # 打印信息
self.id = id_val # 初始化 id
class User(BaseEntity):
def __new__(cls, *args, **kwargs): # 子类的 __new__
print(f"--- User.__new__ 被调用 (cls: {
cls.__name__}) ---") # 打印信息
# 即使 User 自己的 __new__ 没有显式地创建实例
# 它仍然需要调用 super().__new__(cls) 来委托给 BaseEntity.__new__
# 从而确保整个继承链上的对象创建逻辑都被执行
instance = super().__new__(cls) # 调用 BaseEntity.__new__
instance._user_creation_info = "User class processed new" # 在子类中添加属性
return instance # 返回实例
def __init__(self, id_val, name): # 子类的 __init__
print(f"--- User.__init__ 被调用 (id: {
id_val}, name: {
name}) ---") # 打印信息
super().__init__(id_val) # 调用基类的 __init__
self.name = name # 初始化 name
print("创建 User 实例:")
user_obj = User(101, "Alice") # 创建 User 实例
print(f"\nuser_obj.id: {
user_obj.id}") # 访问 id 属性
print(f"user_obj.name: {
user_obj.name}") # 访问 name 属性
print(f"user_obj._base_creation_info: {
user_obj._base_creation_info}") # 访问基类添加的属性
print(f"user_obj._user_creation_info: {
user_obj._user_creation_info}") # 访问子类添加的属性
print(f"user_obj 的类型: {
type(user_obj)}") # 打印类型
代码解释:
class BaseEntity: 和 class User(BaseEntity)::定义了基类 BaseEntity 和继承自它的子类 User。
BaseEntity.__new__:这个 __new__ 方法在创建实例时打印了一条消息,并通过 super().__new__(cls) 调用了 object.__new__ 来实际创建实例,并设置了一个 _base_creation_info 属性。
User.__new__:这个 __new__ 方法同样打印了一条消息,并通过 super().__new__(cls) 调用了 BaseEntity.__new__。注意,这里的 cls 仍然是 User 类。它也设置了一个 _user_creation_info 属性。
当执行 user_obj = User(101, "Alice") 时:
User(101, "Alice") 被调用。
Python 首先查找 User 类的 __new__ 方法。
User.__new__ 被调用,cls 是 User。它打印消息并执行 instance = super().__new__(cls)。
super().__new__(cls) (在 User 的 __new__ 中) 会向上查找方法解析顺序 (MRO),找到 BaseEntity.__new__。
BaseEntity.__new__ 被调用,cls 仍然是 User。它打印消息并执行 instance = super().__new__(cls)。
super().__new__(cls) (在 BaseEntity 的 __new__ 中) 会向上查找方法解析顺序,找到 object.__new__。
object.__new__(User) 被调用,实际创建了一个 User 类型的对象实例。
这个实例被返回给 BaseEntity.__new__,BaseEntity.__new__ 在其上设置 _base_creation_info,然后返回。
这个实例又被返回给 User.__new__,User.__new__ 在其上设置 _user_creation_info,然后返回。
最终,这个已经被两个 __new__ 方法处理过的 User 实例被返回给 Python 解释器。
解释器发现返回的是 User 实例,于是调用 User.__init__(user_obj, 101, "Alice")。
User.__init__ 调用 super().__init__(id_val),即 BaseEntity.__init__,来初始化 id。
User.__init__ 继续初始化 name。
输出结果清晰地展示了 __new__ 方法如何沿着继承链从最底层向上传递和执行,而 __init__ 则从子类向下调用父类的 __init__。这正是 super() 的精髓和 MRO 的应用。
2.4 __new__ 与 __init__ 的参数传递协同
虽然 __new__ 和 __init__ 都接收实例化时传入的参数,但它们的参数签名可以不同。重要的是,__new__ 返回的对象会作为 __init__ 的 self 参数,而 __new__ 收到的 *args 和 **kwargs 会直接传递给 __init__。
这意味着:
如果 __new__ 接收 (cls, a, b, *args, **kwargs),那么 __init__ 也会尝试接收 (self, a, b, *args, **kwargs)。
如果 __new__ 返回的对象类型与请求的 cls 不同,那么 __init__ 就不会被调用。
示例:参数传递的微妙之处
class ComplexParameterFlow:
def __new__(cls, arg_from_new, *args_new, **kwargs_new): # __new__ 定义了第一个参数和可变参数
print(f"\n--- ComplexParameterFlow.__new__ 被调用 ---") # 打印信息
print(f"arg_from_new: {
arg_from_new}") # 打印 arg_from_new
print(f"args_new: {
args_new}") # 打印 args_new
print(f"kwargs_new: {
kwargs_new}") # 打印 kwargs_new
# 可以在 __new__ 中对参数进行一些处理,然后传递给 super().__new__
# 但通常 super().__new__ 不会使用这些参数,它只关注 cls
# 这里只是为了展示参数的传递
instance = super().__new__(cls) # 创建实例
# 可以在这里保存一些来自 __new__ 的参数信息
instance._new_processed_arg = arg_from_new * 2 # 在 __new__ 中处理参数并设置属性
return instance # 返回实例
def __init__(self, init_arg_1, init_arg_2, *args_init, **kwargs_init): # __init__ 定义了前两个参数和可变参数
print(f"\n--- ComplexParameterFlow.__init__ 被调用 ---") # 打印信息
self.init_arg_1 = init_arg_1 # 初始化 init_arg_1
self.init_arg_2 = init_arg_2 # 初始化 init_arg_2
self.all_other_args = args_init # 初始化 all_other_args
self.all_other_kwargs = kwargs_init # 初始化 all_other_kwargs
print(f"init_arg_1: {
self.init_arg_1}") # 打印 init_arg_1
print(f"init_arg_2: {
self.init_arg_2}") # 打印 init_arg_2
print(f"args_init: {
self.all_other_args}") # 打印 args_init
print(f"kwargs_init: {
self.all_other_kwargs}") # 打印 kwargs_init
print(f"从 __new__ 设置的属性: {
self._new_processed_arg}") # 打印从 __new__ 设置的属性
print("创建 ComplexParameterFlow 实例:")
# 注意:传入的参数将首先被 __new__ 接收,然后传递给 __init__
# 因此,这些参数必须能同时满足 __new__ 和 __init__ 的签名要求
obj_flow = ComplexParameterFlow(
10, # 对应 __new__ 的 arg_from_new,以及 __init__ 的 init_arg_1
"hello", # 对应 __new__ 的 args_new[0],以及 __init__ 的 init_arg_2
True, # 对应 __new__ 的 args_new[1],以及 __init__ 的 args_init[0]
key_a=100, # 对应 __new__ 的 kwargs_new['key_a'],以及 __init__ 的 kwargs_init['key_a']
key_b="world" # 对应 __new__ 的 kwargs_new['key_b'],以及 __init__ 的 kwargs_init['key_b']
)
print(f"\n最终对象 obj_flow 的属性:") # 打印最终对象属性信息
print(f"obj_flow.init_arg_1: {
obj_flow.init_arg_1}") # 访问 init_arg_1
print(f"obj_flow.init_arg_2: {
obj_flow.init_arg_2}") # 访问 init_arg_2
print(f"obj_flow.all_other_args: {
obj_flow.all_other_args}") # 访问 all_other_args
print(f"obj_flow.all_other_kwargs: {
obj_flow.all_other_kwargs}") # 访问 all_other_kwargs
print(f"obj_flow._new_processed_arg: {
obj_flow._new_processed_arg}") # 访问 _new_processed_arg
代码解释:
class ComplexParameterFlow::定义一个名为 ComplexParameterFlow 的类。
def __new__(cls, arg_from_new, *args_new, **kwargs_new)::__new__ 方法定义了一个明确的位置参数 arg_from_new,以及捕获剩余位置参数的 *args_new 和关键字参数的 **kwargs_new。
instance._new_processed_arg = arg_from_new * 2:在 __new__ 中,我们处理了 arg_from_new 并将其结果存储在新创建的实例的一个属性中。这表明 __new__ 可以在 __init__ 之前对参数进行处理,并将处理结果附加到对象上。
def __init__(self, init_arg_1, init_arg_2, *args_init, **kwargs_init)::__init__ 方法也定义了两个明确的位置参数 init_arg_1, init_arg_2,以及捕获剩余参数的 *args_init 和 **kwargs_init。
obj_flow = ComplexParameterFlow(...):当调用类时,所有参数首先传递给 __new__。
10 匹配 __new__ 的 arg_from_new。
"hello", True 匹配 __new__ 的 *args_new。
key_a=100, key_b="world" 匹配 __new__ 的 **kwargs_new。
在 __new__ 返回实例后,Python 将同样地将原始的 (10, "hello", True, key_a=100, key_b="world") 这些参数传递给 __init__。
10 匹配 __init__ 的 init_arg_1。
"hello" 匹配 __init__ 的 init_arg_2。
True 匹配 __init__ 的 *args_init 中的第一个元素。
key_a=100, key_b="world" 匹配 __init__ 的 **kwargs_init。
这个例子展示了参数从类调用点到 __new__ 再到 __init__ 的完整流动路径,以及在这两个魔术方法中如何对这些参数进行分别处理和利用。
2.5 总结基本机制
至此,我们已经深入了解了 __new__ 方法的基本机制:
它是对象的创建者,在 __init__ 之前被调用。
它的第一个参数是类本身 (cls)。
它接收并透传所有实例化时传入的参数 (*args, **kwargs)。
它必须返回一个对象实例。如果返回的实例是请求类或其子类的实例,则会触发 __init__;否则,__init__ 将被跳过。
super().__new__(cls) 是创建标准 Python 对象实例的关键。
掌握这些基本概念是构建更复杂 __new__ 用例的基石。在下一章,我们将进入 __new__ 的实际应用场景,从最常见的单例模式开始。
第三章:__new__ 的经典应用——单例模式的极致实现
单例模式 (Singleton Pattern) 是设计模式中最简单也最广为人知的一种,其核心思想是确保一个类在任何时候都只有一个实例。无论你尝试创建多少次,都会得到同一个实例。这种模式在需要全局唯一资源的场景中非常有用,例如配置管理器、日志记录器、线程池或数据库连接等。
在 Python 中,实现单例模式有多种方式,但利用 __new__ 方法无疑是最“Pythonic”且功能强大的方法之一,因为它直接干预了对象的创建过程,从而从根本上保证了实例的唯一性。
3.1 为什么 __new__ 是实现单例模式的理想选择?
我们回顾一下 __new__ 的特性:它是在 __init__ 之前被调用的,并且它的返回值决定了最终的对象实例。这正是实现单例模式所需要的:
在对象创建之前进行检查:在真正分配内存和构建新对象之前,__new__ 允许我们检查是否已经存在一个实例。
返回现有实例:如果发现实例已经存在,__new__ 可以直接返回那个已有的实例,而不是创建新的。
避免重复初始化:由于 __new__ 返回了现有实例,Python 将不会再次调用 __init__ (因为它不是一个新的 cls 实例),从而避免了对同一个实例进行不必要的或有害的重复初始化。
相较于其他通过 __init__ 或模块级变量实现的单例模式,__new__ 方法实现更为健壮和符合直觉。
3.2 基于 __new__ 的基本单例模式实现
最直接的单例模式实现方式是:在 __new__ 方法内部,检查类的私有属性是否已经存储了实例。如果没有,则创建实例并存储;如果已经存在,则直接返回存储的实例。
class Singleton:
_instance = None # 类属性,用于存储唯一的实例,初始为 None
def __new__(cls, *args, **kwargs): # __new__ 方法是创建对象的入口
print(f"\n--- Singleton.__new__ 被调用 (cls: {
cls.__name__}) ---") # 打印信息
if cls._instance is None: # 检查类属性 _instance 是否为 None (即是否还没有实例)
print("--- 首次创建实例 ---") # 打印首次创建信息
# 调用父类的 __new__ 方法来创建真正的实例对象
# super().__new__(cls) 负责在内存中分配空间并返回一个对象
cls._instance = super().__new__(cls) # 创建实例并将其赋值给 _instance
else:
print("--- 实例已存在,返回现有实例 ---") # 打印返回现有实例信息
return cls._instance # 无论是新创建的还是已存在的,都返回 _instance
def __init__(self, name="default"): # __init__ 方法用于初始化实例
# 只有当 __new__ 返回的是“新”创建的实例时,__init__ 才会被调用
# 在单例模式中,__init__ 会被多次调用(每次尝试实例化都会),但它只应该初始化一次
if not hasattr(self, '_initialized'): # 检查实例是否已经被初始化过,防止重复初始化
self.name = name # 初始化 name 属性
self._initialized = True # 设置初始化标记
print(f"--- Singleton 实例 {
self.name} 已被首次初始化 ---") # 打印初始化信息
else:
print(f"--- Singleton 实例 {
self.name} 已经初始化过,跳过重复初始化 ---") # 打印跳过重复初始化信息
print("尝试创建第一个 Singleton 实例:")
s1 = Singleton("Logger") # 第一次创建实例
print("\n尝试创建第二个 Singleton 实例:")
s2 = Singleton("ConfigManager") # 第二次创建实例
print("\n尝试创建第三个 Singleton 实例:")
s3 = Singleton("DatabaseConnection") # 第三次创建实例
print("\n验证实例是否相同:")
print(f"s1 is s2: {
s1 is s2}") # 检查 s1 和 s2 是否是同一个对象 (内存地址相同)
print(f"s1 is s3: {
s1 is s3}") # 检查 s1 和 s3 是否是同一个对象
print(f"s2 is s3: {
s2 is s3}") # 检查 s2 和 s3 是否是同一个对象
print(f"\ns1 的 name: {
s1.name}") # 访问 s1 的 name 属性
print(f"s2 的 name: {
s2.name}") # 访问 s2 的 name 属性 (会是 'Logger',因为 __init__ 只执行了一次实际初始化)
print(f"s3 的 name: {
s3.name}") # 访问 s3 的 name 属性
代码解释:
class Singleton::定义一个名为 Singleton 的类。
_instance = None:这是一个类属性,用于存储 Singleton 类的唯一实例。初始时为 None。
def __new__(cls, *args, **kwargs)::重写 __new__ 方法。
if cls._instance is None::这是关键的检查点。如果 _instance 为 None,说明这是第一次请求创建实例。
cls._instance = super().__new__(cls):在这种情况下,我们调用父类 object 的 __new__ 方法来创建实际的 Singleton 实例,并将其赋值给 _instance 类属性。
else::如果 _instance 不为 None,说明实例已经存在。
return cls._instance:无论是否存在,__new__ 都返回 cls._instance。这保证了每次调用 Singleton() 都返回同一个对象。
def __init__(self, name="default")::重写 __init__ 方法。
if not hasattr(self, '_initialized')::这是处理单例模式中 __init__ 会被多次调用的关键。每次通过 Singleton() 创建“实例”(即使是返回已存在实例),__init__ 都会被触发。为了防止重复初始化属性,我们引入一个 _initialized 标记。
self.name = name 和 self._initialized = True:只有当 _initialized 标记不存在时,才真正执行初始化逻辑。
运行结果分析:
第一次 s1 = Singleton("Logger"):__new__ 发现 _instance 是 None,创建新实例并存储。然后 __init__ 被调用,设置 name 为 “Logger” 并设置 _initialized。
第二次 s2 = Singleton("ConfigManager"):__new__ 发现 _instance 不为 None,直接返回已有的 s1 实例。接着 __init__ 被调用。由于 s1 (self) 已经有了 _initialized 标记,__init__ 的初始化逻辑被跳过。所以 s2.name 仍然是 “Logger”。
第三次 s3 = Singleton("DatabaseConnection"):同理,__new__ 返回已有实例,__init__ 跳过初始化。
s1 is s2 和 s1 is s3 都会输出 True,证明它们确实是同一个对象。
这个实现优雅地利用了 __new__ 的特性,确保了实例的唯一性。同时,对 __init__ 的额外处理也确保了属性不会被意外地重复初始化。
3.3 进阶单例:参数敏感的单例模式?
有时候,你可能需要根据传入的参数来决定是否返回一个“新”的单例,或者说,维护多个基于不同参数的“单例”。这听起来有点矛盾,因为单例的核心是“唯一”。但实际应用中,这可能是指“对于一组特定的参数,只存在一个实例”。
例如,一个数据库连接池,你可能希望每个数据库的连接池是一个单例,但不同数据库(不同连接字符串)的连接池是独立的单例。
这种情况下,__new__ 仍然是理想的选择,但我们需要一个存储所有“单例”实例的字典。
class ParametrizedSingleton:
_instances = {
} # 类属性,用于存储多个单例实例,键可以是参数的元组,值是对应的实例
def __new__(cls, *args, **kwargs): # __new__ 接收所有参数
print(f"\n--- ParametrizedSingleton.__new__ 被调用 (cls: {
cls.__name__}) ---") # 打印信息
# 将传入的参数作为唯一标识,通常是一个元组,确保可哈希
# 这里我们假设 args 和 kwargs 的组合可以作为唯一键
# 注意:实际应用中需要更严谨地处理参数,例如对字典进行排序以保证键的唯一性
key = (args, frozenset(kwargs.items())) # 将参数打包成可哈希的键
if key not in cls._instances: # 检查这个键对应的实例是否已存在
print(f"--- 首次创建带有键 '{
key}' 的实例 ---") # 打印首次创建信息
instance = super().__new__(cls) # 创建新实例
cls._instances[key] = instance # 存储新实例到 _instances 字典中
# 标记为未初始化,以便 __init__ 可以执行
instance._initialized_with_params = False # 设置初始化标记
else:
print(f"--- 键 '{
key}' 对应的实例已存在,返回现有实例 ---") # 打印返回现有实例信息
instance = cls._instances[key] # 获取现有实例
return instance # 返回实例
def __init__(self, *args, **kwargs): # __init__ 接收所有参数
# 只有当这个实例首次被创建(即 _initialized_with_params 为 False)时才进行初始化
if not self._initialized_with_params: # 检查是否已经初始化过
self.creation_args = args # 存储创建时的位置参数
self.creation_kwargs = kwargs # 存储创建时的关键字参数
self._initialized_with_params = True # 设置初始化标记为 True
print(f"--- ParametrizedSingleton 实例已被首次初始化,参数为: {
args}, {
kwargs} ---") # 打印初始化信息
else:
print(f"--- ParametrizedSingleton 实例已初始化过,跳过重复初始化 ---") # 打印跳过信息
# 情景1: 创建基于参数 (1, 'alpha') 的单例
print("创建实例 ps1 (1, 'alpha'):")
ps1 = ParametrizedSingleton(1, 'alpha', type='basic') # 创建实例
# 情景2: 再次创建基于相同参数的单例
print("\n创建实例 ps2 (1, 'alpha'):")
ps2 = ParametrizedSingleton(1, 'alpha', type='basic') # 再次创建相同参数的实例
# 验证 ps1 和 ps2 是否相同
print(f"\nps1 is ps2: {
ps1 is ps2}") # 检查是否是同一个对象
# 情景3: 创建基于不同参数的单例
print("\n创建实例 ps3 (2, 'beta'):")
ps3 = ParametrizedSingleton(2, 'beta', type='advanced') # 创建不同参数的实例
# 验证 ps1 和 ps3 是否不同
print(f"ps1 is ps3: {
ps1 is ps3}") # 检查是否是同一个对象
# 验证 ps1 的属性
print(f"\nps1 的创建参数: {
ps1.creation_args}, {
ps1.creation_kwargs}") # 访问 ps1 的创建参数
# 验证 ps3 的属性
print(f"ps3 的创建参数: {
ps3.creation_args}, {
ps3.creation_kwargs}") # 访问 ps3 的创建参数
# 尝试用不同顺序的 kwargs 创建 (对于 frozenset 是相同的键)
print("\n创建实例 ps4 (1, 'alpha', type='basic', other='value'):")
ps4 = ParametrizedSingleton(1, 'alpha', other='value', type='basic') # 相同参数,不同 kwargs 顺序
print(f"\nps1 is ps4: {
ps1 is ps4}") # 检查 ps1 和 ps4 是否相同 (应该为 False,因为 other='value' 使 kwargs 不同)
print(f"ps4 的创建参数: {
ps4.creation_args}, {
ps4.creation_kwargs}") # 访问 ps4 的创建参数
代码解释:
_instances = {}:类属性,现在是一个字典,用于存储不同参数组合对应的单例实例。
key = (args, frozenset(kwargs.items())):这是核心。它将所有位置参数 (args) 作为一个元组,并将所有关键字参数 (kwargs) 的 items() 转换为 frozenset。frozenset 是不可变的,因此可以作为字典的键。这种方式使得无论 kwargs 内部键值对的顺序如何,只要内容相同,生成的 frozenset 就是一样的,从而保证了键的唯一性。
if key not in cls._instances::检查当前参数组合是否已经存在对应的单例实例。
instance = super().__new__(cls) 和 cls._instances[key] = instance:如果不存在,则创建新实例并将其存储到 _instances 字典中。
instance._initialized_with_params = False:为了正确处理 __init__ 的重复调用问题,我们在这里给新实例添加一个初始化标记,确保 __init__ 只在实例首次创建时执行。
__init__ 方法:与前面的基本单例类似,通过 self._initialized_with_params 标记来防止重复初始化。
运行结果分析:
ps1 和 ps2 具有完全相同的参数 ((1, 'alpha'), frozenset([('type', 'basic')])),因此它们是同一个实例。
ps3 具有不同的参数 ((2, 'beta'), frozenset([('type', 'advanced')])),因此它是一个新的、独立的实例。
ps4 传入的 kwargs 中多了一个 other='value',这使得其 frozenset 键不同于 ps1 的,因此 ps4 是一个新实例。这说明了 key 的构建逻辑直接决定了“单例”的粒度。
这个“参数敏感”的单例模式,实际上是工厂模式和单例模式的一种结合,__new__ 在这里扮演了“根据输入参数决定返回哪一个(现有或新建)实例”的关键角色。
3.4 单例模式的局限性与注意事项
尽管 __new__ 在实现单例模式上非常强大,但使用时仍需注意其局限性和潜在问题:
参数一致性问题:如果你的单例类有 __init__ 方法,并且每次尝试实例化时都传入不同的参数,那么只有第一次初始化时传入的参数会生效。后续的 __init__ 调用会被跳过(如果做了前面示例中的 _initialized 检查),或者对已存在的实例进行重复初始化(如果没有检查),这可能会导致意外的行为。因此,单例模式通常假设所有对构造器的调用都应该使用兼容的参数,或者只有第一次调用是真正有意义的。
继承问题:当子类继承一个单例类时,如果不小心处理,子类可能仍然会创建自己的单例实例,而不是共享父类的实例。或者,子类可能会破坏父类的单例逻辑。通常,如果一个类是单例,它的子类也应该被限制为单例。
测试的复杂性:单例模式引入了全局状态,这使得单元测试变得更加复杂。你可能需要某种机制来“重置”单例,以便在不同的测试用例之间进行隔离。
违反开放/封闭原则:单例模式有时被认为是“反模式”,因为它将创建逻辑和业务逻辑耦合在一起,使得系统不够灵活。在一些场景下,依赖注入 (Dependency Injection) 可能是更好的替代方案,它允许你显式地控制依赖关系的创建和传递。
多线程安全:上述单例实现(包括基本单例和参数敏感单例)在多线程环境下可能存在竞争条件。如果多个线程同时首次请求实例,可能会导致创建多个实例。解决这个问题需要引入线程锁。
3.4.1 线程安全的单例实现
为了确保在多线程环境下的单例模式的正确性,我们需要引入锁机制,通常是 threading.Lock。
import threading # 导入 threading 模块
class ThreadSafeSingleton:
_instance = None # 类属性,用于存储唯一的实例
_lock = threading.Lock() # 创建一个线程锁,用于保护 _instance 的并发访问
def __new__(cls, *args, **kwargs): # __new__ 方法
print(f"\n--- ThreadSafeSingleton.__new__ 被调用 (cls: {
cls.__name__}) ---") # 打印信息
# 在获取锁之前,先进行一次快速检查
# 这种“双重检查锁定”(Double-Checked Locking)模式可以减少锁的竞争,提高性能
if cls._instance is None: # 第一次检查:实例是否已存在
with cls._lock: # 使用 with 语句获取锁,确保线程安全
# 在获取锁之后,再次检查实例是否已存在
# 这是因为在第一个线程检查到 None 并进入锁之前,
# 可能另一个线程已经创建了实例
if cls._instance is None: # 第二次检查:确保在锁内实例确实未创建
print("--- (在锁内) 首次创建线程安全实例 ---") # 打印信息
cls._instance = super().__new__(cls) # 创建实例
else:
print("--- (在锁内) 实例已在其他线程中创建,跳过创建 ---") # 打印信息
else:
print("--- (锁外) 实例已存在,返回现有实例 ---") # 打印信息
return cls._instance # 返回实例
def __init__(self, name="TS_Default"): # __init__ 方法
# 同样的初始化检查,防止重复初始化
if not hasattr(self, '_initialized'): # 检查是否已经初始化
self.name = name # 初始化 name 属性
self._initialized = True # 设置初始化标记
print(f"--- ThreadSafeSingleton 实例 {
self.name} 已被首次初始化 ---") # 打印信息
else:
print(f"--- ThreadSafeSingleton 实例 {
self.name} 已经初始化过,跳过重复初始化 ---") # 打印信息
# 模拟多线程创建单例
def create_singleton_instance(thread_id): # 定义一个函数,模拟线程创建单例
print(f"线程 {
thread_id}: 尝试创建单例...") # 打印线程尝试创建单例的信息
instance = ThreadSafeSingleton(f"Instance_from_Thread_{
thread_id}") # 创建单例实例
print(f"线程 {
thread_id}: 获取到实例: {
instance}, name: {
instance.name}") # 打印线程获取到的实例信息
return instance # 返回实例
threads = [] # 存储线程的列表
results = [] # 存储线程返回结果的列表
num_threads = 5 # 模拟 5 个线程
for i in range(num_threads): # 循环创建线程
# target 是线程要执行的函数,args 是传递给函数的参数
thread = threading.Thread(target=lambda: results.append(create_singleton_instance(i))) # 创建线程
threads.append(thread) # 将线程添加到列表中
thread.start() # 启动线程
for thread in threads: # 等待所有线程完成
thread.join() # 等待线程结束
print("\n--- 所有线程完成创建尝试 ---") # 打印所有线程完成信息
print(f"所有线程获取的实例是否都相同? {
all(r is results[0] for r in results)}") # 检查所有实例是否都相同
print(f"最终单例实例的名称: {
ThreadSafeSingleton._instance.name}") # 打印最终单例实例的名称
代码解释:
import threading:导入 threading 模块以使用线程相关功能。
_lock = threading.Lock():创建一个 threading.Lock 对象,这是一个互斥锁,用于控制对共享资源(在这里是 _instance)的访问。
if cls._instance is None: (第一次检查):在尝试获取锁之前,先快速检查 _instance。如果它已经存在,就直接返回,避免不必要的锁竞争。这被称为双重检查锁定 (Double-Checked Locking) 模式。
with cls._lock::这是一个上下文管理器,它确保在代码块执行前获取锁,并在代码块执行完毕(无论正常结束还是发生异常)后自动释放锁。
if cls._instance is None: (第二次检查):在获取锁之后,再次检查 _instance 是否为 None。这是必要的,因为在第一个线程进行第一次检查后,可能其他线程在此期间已经获取到锁并创建了实例,然后释放了锁。如果没有第二次检查,当前线程可能会再次创建实例,破坏单例。
create_singleton_instance(thread_id):这是一个用于模拟多线程并发访问的函数。
threading.Thread(target=lambda: results.append(create_singleton_instance(i))):创建线程并将其目标设置为一个 lambda 函数,该函数会调用 create_singleton_instance 并将其结果添加到 results 列表中。
thread.start() 和 thread.join():启动所有线程并等待它们全部执行完毕。
all(r is results[0] for r in results):检查 results 列表中所有获取到的实例是否都与第一个实例相同,以验证单例是否成功。
这个线程安全的单例实现,通过双重检查锁定和 threading.Lock 确保了在多线程并发环境下,__new__ 只会真正创建一次实例,从而有效地维护了单例模式的唯一性约束。
3.5 总结单例模式
通过 __new__ 实现单例模式是 Python 中非常优雅且强大的方法。它允许我们在对象实例化之前就进行控制,从而实现实例的唯一性。虽然其基本实现相对简单,但在考虑参数化单例和多线程安全时,需要更细致的设计。理解 __new__ 在这里的核心作用,有助于我们更好地掌握 Python 对象的生命周期管理。
第四章:__new__ 与不可变对象:构建固若金汤的数据结构
在 Python 编程中,数据的可变性是一个核心概念。有些对象(如列表、字典)是可变的,它们在创建后可以被修改;而另一些对象(如数字、字符串、元组)是不可变的,它们一旦创建,其内部状态就不能被改变。不可变对象在并发编程、函数式编程以及缓存优化等方面具有显著优势。
本章将深入探讨不可变对象的本质,并着重阐述 __new__ 方法如何在自定义不可变类中扮演关键角色,确保对象一旦创建便无法更改其核心状态。我们将从最基础的不可变概念开始,逐步构建复杂的不可变数据结构,并分析其背后的设计哲学与性能考量。
4.1 不可变对象的本质与优势
4.1.1 什么是不可变对象?
一个不可变对象是指在其生命周期内,其内部状态(即其属性的值)在创建后就不能被修改的对象。这意味着,一旦你创建了一个不可变对象,你就不能改变它所代表的值。如果需要表示一个不同的值,你必须创建一个全新的对象。
例如,Python 内置的整型 (int)、浮点型 (float)、字符串 (str)、元组 (tuple)、frozenset 都是不可变对象。
# 示例:Python 内置不可变类型
my_int = 10 # 创建一个整数对象
print(f"原始整数: {
my_int}, ID: {
id(my_int)}") # 打印整数值和内存地址
# 尝试“修改”整数
my_int = my_int + 5 # 这一行并不是修改了原有的 10,而是创建了一个新的整数对象 15,然后让 my_int 指向它
print(f"修改后整数: {
my_int}, ID: {
id(my_int)}") # 打印新的整数值和内存地址,ID 会不同
my_string = "Hello" # 创建一个字符串对象
print(f"原始字符串: {
my_string}, ID: {
id(my_string)}") # 打印字符串值和内存地址
# 尝试“修改”字符串
my_string = my_string + " World" # 同样,这不是修改原字符串,而是创建新字符串 "Hello World",然后让 my_string 指向它
print(f"修改后字符串: {
my_string}, ID: {
id(my_string)}") # 打印新的字符串值和内存地址,ID 会不同
my_tuple = (1, 2, 3) # 创建一个元组对象
print(f"原始元组: {
my_tuple}, ID: {
id(my_tuple)}") # 打印元组值和内存地址
# 尝试修改元组元素(会报错)
# my_tuple[0] = 10 # 这一行会引发 TypeError: 'tuple' object does not support item assignment
# print(f"尝试修改元组后: {my_tuple}")
# 如果要“改变”元组,必须创建新元组
new_tuple = my_tuple + (4,) # 创建一个新的元组,而不是修改旧的
print(f"新元组: {
new_tuple}, ID: {
id(new_tuple)}") # 打印新元组值和内存地址,ID 会不同
print(f"原始元组是否改变: {
my_tuple}") # 原始元组保持不变
代码解释:
my_int = 10:创建一个整数对象 10,并让变量 my_int 指向它。
print(f"原始整数: {my_int}, ID: {id(my_int)}"):打印 my_int 的当前值和其在内存中的唯一标识符(ID)。
my_int = my_int + 5:执行加法操作。由于整数是不可变的,这个操作实际上是计算 10 + 5 得到结果 15,然后创建一个新的整数对象 15,并将 my_int 变量的引用从 10 更改为 15。原来的 10 对象如果不再被引用,最终会被垃圾回收。
print(f"修改后整数: {my_int}, ID: {id(my_int)}"):再次打印 my_int 的值和 ID。您会发现 ID 发生了变化,这证明 my_int 现在指向了一个新的对象。
my_string = "Hello":创建一个字符串对象 "Hello"。
my_string = my_string + " World":字符串拼接操作。与整数类似,这不是在原地修改 "Hello",而是创建一个新的字符串 "Hello World",并更新 my_string 的引用。
my_tuple = (1, 2, 3):创建一个元组对象 (1, 2, 3)。
# my_tuple[0] = 10:这行代码被注释掉,如果取消注释执行,会引发 TypeError,因为元组的元素不能在创建后被修改。这正是不可变性的体现。
new_tuple = my_tuple + (4,):要“改变”元组,必须通过创建新元组的方式。这里是将 my_tuple 与一个包含 4 的新元组拼接,生成一个全新的元组 (1, 2, 3, 4),并赋值给 new_tuple。原始的 my_tuple 保持不变。
4.1.2 不可变对象的优势
不可变对象在软件设计和实现中带来了诸多优势:
线程安全:
核心优势:由于不可变对象的状态在其生命周期内不会改变,因此多个线程可以同时安全地访问同一个不可变对象,而无需担心数据竞争或加锁。这大大简化了并发编程,避免了死锁、活锁等复杂问题。
深入理解:在多线程环境中,可变对象的共享访问需要同步机制(如锁),以防止一个线程在读取时另一个线程修改了数据。不可变对象天然地消除了这种需求,因为它们永远不会被修改。这意味着它们是“写一次读多次”的理想数据模型,无需同步开销。
易于推理和调试:
确定性行为:不可变对象的状态在创建时就被确定,并且永远不会改变。这意味着在程序的任何一点访问该对象,其值都是可预测的。
简化调试:当程序出现问题时,你可以确定不可变对象的值不会在某个意外的地方被偷偷修改,这大大缩小了问题排查的范围。你不需要担心一个对象在某个函数调用后突然变成了你不期望的样子。
用作字典键或集合元素:
哈希性要求:在 Python 中,只有可哈希 (hashable) 的对象才能作为字典的键或集合的元素。一个对象是可哈希的,如果它有一个 __hash__ 方法并且其值(通过 __eq__ 方法比较)在其生命周期内保持不变。
不可变性与哈希性:不可变对象天然满足哈希性的要求,因为它们的内部状态不会改变,因此它们的哈希值在创建后也是固定的。这使得它们非常适合用作缓存键、查找表中的键等。
提高性能(缓存和共享):
安全缓存:由于不可变对象不会改变,你可以安全地缓存它们的哈希值或计算结果。一旦计算出某个不可变对象的结果,就可以将其缓存起来,下次需要时直接使用,无需重新计算。
内存共享:当多个变量引用同一个不可变对象时,它们实际上共享了同一块内存区域。这比为每个变量复制数据更节省内存。例如,多个字符串变量引用相同内容的字符串时,Python 解释器可能会进行字符串驻留 (string interning) 优化,使得它们指向同一内存地址。
支持函数式编程:
纯函数:在函数式编程范式中,强调使用纯函数 (pure functions)。纯函数是指给定相同的输入,总是返回相同的输出,并且没有副作用(不修改外部状态)。不可变对象是实现纯函数的理想基础,因为它们确保函数不会通过修改输入参数来产生副作用。
链式操作:许多操作(如字符串方法 replace()、upper() 等)虽然看起来像修改了对象,但实际上是返回了一个新的字符串对象。这种链式操作在处理不可变数据时非常自然。
4.1.3 何时选择不可变对象?
配置和设置:应用程序的配置通常在启动时加载一次,之后不应更改。
状态快照:需要记录某个时间点的数据状态,而不希望它后续被修改。
消息传递:在系统组件之间传递的数据,作为消息载体,不应在传输过程中被修改。
并发访问的数据:任何会被多个线程读取但不需要写入的数据。
作为字典键或集合元素:如果需要自定义对象作为这些数据结构的键或元素。
4.2 __new__ 在不可变对象创建中的作用
__new__ 方法在创建不可变对象时发挥着至关重要的作用。它的独特能力——在对象被完全构造并传递给 __init__ 之前进行干预——使其成为强制不可变性的完美场所。
4.2.1 __init__ 的局限性
如果仅仅依赖 __init__ 来实现不可变性,是存在局限的。__init__ 的任务是初始化一个已经存在的对象实例。这意味着,__init__ 内部仍然可以修改 self 对象的属性。即使你在 __init__ 中将属性设置为只读(例如,通过 property 或 __slots__),也只能限制外部访问者或后续方法对这些属性的修改,但无法阻止 __init__ 自己在初始设置后对属性的进一步修改(尽管不常见)。
更重要的是,__init__ 无法阻止在对象创建后通过直接操作 __dict__(如果类没有使用 __slots__)或通过某些内部 Python 机制来修改属性。
4.2.2 __new__ 的介入点
__new__ 方法在对象真正诞生之前就介入了。它返回的是一个原始的、通常尚未完全初始化的对象实例。这意味着:
控制实例的创建:__new__ 可以确保在实例被创建时,其关键的、定义其不可变状态的属性就已经被设定,并且这些属性的设置方式不会在后续被轻易绕过。
返回经过“固化”的对象:你可以利用 __new__ 在返回对象之前,对对象进行任何必要的“固化”操作,例如将其所有属性设置为只读,或者将其内部的可变数据结构转换为不可变形式。
与 __slots__ 的协同:__new__ 与 __slots__ 配合使用可以更有效地创建不可变对象。__slots__ 机制可以限制实例属性的数量,并通常阻止在对象创建后添加新的属性,从而避免了 __dict__ 的开销,并有助于在编译时就确定对象的布局,从而节省内存。
4.3 实现一个自定义不可变类
我们将创建一个简单的自定义不可变类 ImmutablePoint,代表一个二维坐标点。一旦创建,其 x 和 y 坐标就不能被改变。
4.3.1 基本不可变类 (__slots__ 和私有属性)
为了实现不可变性,我们将结合 __new__ 和 __slots__,并使用一些约定来限制属性的修改。
__slots__:定义 __slots__ 可以防止在实例上创建 __dict__,从而节省内存。更重要的是,它也限制了实例在创建后能够拥有的属性,阻止了随意添加新属性。
私有属性命名约定:尽管 Python 没有真正的私有属性,但通过前缀 __ (双下划线) 可以触发名称混淆 (name mangling),使得外部直接访问更加困难,从而强调这些属性是内部的,不应被修改。
移除 __init__ 或限制其功能:在某些情况下,你可以将所有初始化逻辑放在 __new__ 中,甚至完全移除 __init__。但更常见且灵活的做法是让 __init__ 只进行一次性设置,并依赖 __new__ 确保实例的不可变性。
class ImmutablePoint:
# __slots__ 声明了实例只会有这些属性,不会创建 __dict__,节省内存,并防止运行时添加新属性
__slots__ = ('__x', '__y') # 定义实例只能拥有的属性,且这些属性名会被 Python 内部混淆处理
def __new__(cls, x, y): # __new__ 是创建对象的入口,在 __init__ 之前调用
print(f"\n--- ImmutablePoint.__new__ 被调用 (x={
x}, y={
y}) ---") # 打印 __new__ 调用信息
# 调用父类(object)的 __new__ 方法来实际创建对象实例
instance = super().__new__(cls) # 创建一个 ImmutablePoint 实例
# 在 __new__ 中直接设置属性。这是确保不可变的关键步骤。
# 一旦在这里设置,后续就没有简单的方式可以修改这些混淆后的属性。
instance.__x = x # 设置实例的 __x 属性
instance.__y = y # 设置实例的 __y 属性
print(f"--- ImmutablePoint.__new__ 设置属性完成 ---") # 打印属性设置完成信息
return instance # 返回新创建且已设置好“不可变”属性的实例
# 通常对于这种 __new__ 完全处理初始化的场景,__init__ 可以省略或保持精简。
# 这里我们仍然保留一个 __init__ 来展示其执行时机,但它不做实际的属性设置。
def __init__(self, x, y): # __init__ 在 __new__ 返回实例后被调用
print(f"--- ImmutablePoint.__init__ 被调用 (x={
x}, y={
y}) ---") # 打印 __init__ 调用信息
# 警告:不要在这里再次设置 __x 和 __y,因为它们已经在 __new__ 中设置了,
# 且在 __new__ 中设置的混淆名称属性,如果再次通过 self.__x 赋值,会创建新的属性
# 具体来说,self.__x 会被混淆成 self._ImmutablePoint__x。
# 如果在 __init__ 中写 self.__x = x,会再次尝试修改 self._ImmutablePoint__x。
# 正确的做法是,__init__ 知道 __new__ 已经完成了不可变状态的设置。
# 如果需要进行任何基于这些值的只读操作,可以在这里进行。
pass # __init__ 在此例中不做任何实际的属性赋值
# 定义属性访问器,提供外部读取属性的接口
@property # 装饰器,将方法转化为属性,使得可以通过 point.x 方式访问
def x(self): # 定义 x 属性的 getter 方法
return self.__x # 返回内部的 __x 属性的值
@property # 装饰器,将方法转化为属性,使得可以通过 point.y 方式访问
def y(self): # 定义 y 属性的 getter 方法
return self.__y # 返回内部的 __y 属性的值
def __repr__(self): # 定义对象的字符串表示形式,用于调试
# 通过混淆后的名称访问内部属性
return f"ImmutablePoint(x={
self.__x}, y={
self.__y})" # 返回对象的字符串表示
# 创建一个 ImmutablePoint 实例
print("--- 尝试创建点 (5, 10) ---") # 打印创建点的信息
point1 = ImmutablePoint(5, 10) # 调用 ImmutablePoint 类创建实例
print(f"\n创建的实例: {
point1}") # 打印实例的字符串表示
print(f"point1.x: {
point1.x}") # 访问 x 属性
print(f"point1.y: {
point1.y}") # 访问 y 属性
# 尝试修改属性
print("\n--- 尝试修改 point1.x (将失败) ---") # 打印尝试修改信息
try:
point1.x = 20 # 尝试通过 setter 修改属性 (因为没有 @x.setter,会报错)
except AttributeError as e: # 捕获 AttributeError
print(f"捕获到错误: {
e}") # 打印错误信息
print("\n--- 尝试直接设置不存在的属性 (将失败,因为使用了 __slots__) ---") # 打印尝试设置不存在属性信息
try:
point1.z = 30 # 尝试添加一个新属性 (会因为 __slots__ 报错)
except AttributeError as e: # 捕获 AttributeError
print(f"捕获到错误: {
e}") # 打印错误信息
print("\n--- 再次检查 point1 的值是否未变 ---") # 打印检查信息
print(f"point1: {
point1}") # 再次打印实例的字符串表示
# 演示哈希性 (因为不可变,所以可哈希)
# 一个不可变对象必须实现 __hash__ 和 __eq__ 才能作为字典的键或集合的元素
# 默认情况下,如果一个类定义了 __eq__ 但没有定义 __hash__,并且没有继承自 object 的 __hash__,
# 那么它就不是可哈希的。
# 对于使用了 __slots__ 且所有槽属性都是不可变的类,通常会继承 object 的默认 __hash__ 行为,使其可哈希。
# 我们可以显式地添加 __eq__ 和 __hash__ 方法来确保这一点。
class ImmutablePointHashable(ImmutablePoint): # 继承 ImmutablePoint 类
__slots__ = () # 再次声明 __slots__ 为空元组,表示不添加新的槽,但继承父类的槽
def __eq__(self, other): # 定义相等比较方法
# 检查类型是否相同,并比较 x 和 y 属性
return isinstance(other, ImmutablePointHashable) and self.x == other.x and self.y == other.y # 返回比较结果
def __hash__(self): # 定义哈希方法
# 结合 x 和 y 的哈希值,返回一个哈希值
# tuple(self.x, self.y).__hash__() 是一个常见且有效的方法
return hash((self.x, self.y)) # 返回 x 和 y 元组的哈希值
print("\n--- 演示可哈希的 ImmutablePointHashable ---") # 打印演示信息
point2