Python中__new__与__init__的区别:从对象创建到初始化的完整解析

发布时间:2026/8/13 4:54:57
Python中__new__与__init__的区别:从对象创建到初始化的完整解析 1. 项目概述从“创建”与“初始化”的混淆说起如果你写过一段时间的Python尤其是尝试过自定义类那么__init__方法对你来说肯定不陌生。我们习惯性地把它称为“构造方法”在里头给实例属性赋值完成对象的“初始化”。但不知道你有没有遇到过这样的场景你想控制一个类究竟能不能被实例化或者你想让一个类无论怎么new返回的都是同一个实例单例模式又或者你发现__init__方法在某些继承场景下被调用了多次行为诡异。当你去搜索这些问题的解决方案时总会看到一个更底层、更神秘的方法——__new__。很多人对这两个方法的关系是模糊的甚至认为__new__是__init__的“前身”或者“高级版本”。这种误解直接导致了代码设计上的困惑。比如试图在__init__里返回一个其他对象这是无效的或者错误地在__new__里进行大量的属性初始化操作。实际上__new__和__init__在Python对象生命周期中扮演着截然不同、泾渭分明的角色。理解它们不仅仅是掌握两个特殊方法更是理解Python面向对象模型中“对象从无到有”这一核心过程的钥匙。这对于编写健壮的类库、实现设计模式如单例、工厂、理解元类编程乃至进行高级调试都至关重要。简单来说__new__负责“创建”从内存中申请并构建一个原始对象而__init__负责“初始化”对这个已经创建好的对象进行赋值等修饰工作。本篇文章我们就来彻底拆解这对搭档让你不仅知道怎么用更明白为什么要这么用以及在实际项目中如何避开那些常见的“坑”。2. 核心概念拆解__new__与__init__的职责边界要理解这两个方法我们必须先跳出“构造”这个笼统的概念从Python解释器创建对象的实际步骤来看。2.1__new__对象的“造物主”__new__是一个静态方法虽然你用不着加staticmethod装饰器因为它本身就是一个特殊的静态方法它的职责是创建并返回一个新的类实例。它是对象诞生过程中第一个被调用的方法。它的方法签名通常是这样的def __new__(cls, *args, **kwargs)。注意它的第一个参数是cls代表当前正在被实例化的类本身而不是self此时self还不存在。它的返回值必须是一个对象实例通常是调用其父类的__new__方法最常见的是object.__new__(cls)的结果。__new__能做什么控制实例创建过程你可以决定是否真的创建一个新实例。比如在单例模式中__new__会检查类是否已有实例如果有则直接返回该实例不再创建新的。返回其他类型的对象__new__的返回值并不强制必须是cls的实例。你可以让它返回一个整数、一个字符串甚至是另一个完全不相关的类的实例。这虽然不常见但为某些“工厂”或“代理”模式提供了可能性。与不可变类型打交道对于像int,str,tuple这样的不可变类型如果你想在“创建”时就赋予其值必须在__new__中完成因为一旦对象在__new__阶段被创建出来其值在__init__中就无法再被修改了。注意__new__是类方法但Python通过特殊方式处理你无需显式声明。直接定义def __new__(cls, ...)即可。2.2__init__对象的“装修队”__init__是一个实例方法它的职责是初始化一个新创建好的实例。它是在__new__方法成功创建并返回一个实例之后才被调用的。它的第一个参数是self代表已经被__new__创建出来的那个实例对象。它的方法签名是def __init__(self, *args, **kwargs)。它不应该返回任何值或者说返回None。它的作用就是修改self为其添加属性、建立初始状态。__init__能做什么初始化实例属性这是它最主要的工作self.name name,self.value value。建立对象初始状态比如打开一个文件连接、建立一个网络请求会话、初始化一个内部列表或字典。参数验证与转换在赋值前对传入的参数进行检查、格式化或类型转换。一个关键区别__new__可以没有__init__而独立工作比如直接返回一个已有的实例但如果__new__没有返回一个cls的实例或者其子类的实例那么__init__将根本不会被调用。这是很多初学者困惑的地方。2.3 生命周期流程图解让我们用一段代码和注释来直观展示这个流程class MyClass: def __new__(cls, *args, **kwargs): print(f1. __new__ 被调用参数 cls{cls}, args{args}, kwargs{kwargs}) # 必须调用父类的__new__来创建真正的实例对象 instance super().__new__(cls) # 通常就是 object.__new__(cls) print(f 创建出的实例对象是{instance}) return instance # 将这个“半成品”实例返回 def __init__(self, value): print(f2. __init__ 被调用参数 self{self}, value{value}) # 此时self就是__new__返回的那个实例 self.value value print(f 初始化完成self.value{self.value}) # 实例化过程 obj MyClass(42)输出顺序必然是1. __new__ 被调用参数 clsclass __main__.MyClass, args(42,), kwargs{} 创建出的实例对象是__main__.MyClass object at 0x... 2. __init__ 被调用参数 self__main__.MyClass object at 0x..., value42 初始化完成self.value42这个过程清晰地表明先有__new__“造出”对象分配内存生成对象ID后有__init__“装修”这个对象填充数据。3. 深入原理从字节码看实例化过程理解了基本职责我们不妨再深入一层看看Python解释器到底是怎么做的。这能帮你从根本上理解一些怪异行为。我们可以使用dis模块来反汇编实例化过程的字节码。import dis def create_instance(): obj MyClass(42) dis.dis(create_instance)你会看到类似下面的输出简化关键部分... 2 0 LOAD_GLOBAL 0 (MyClass) 2 LOAD_CONST 1 (42) 4 CALL_FUNCTION 1 6 STORE_FAST 0 (obj) ...关键指令是CALL_FUNCTION。当解释器执行MyClass(42)时它实际上调用的是类的__call__方法。而一个类作为元类type的实例的__call__方法其内部逻辑大致如下伪代码所示# 元类type的__call__方法近似逻辑 def __call__(cls, *args, **kwargs): # 1. 调用类的 __new__ 方法创建对象 obj cls.__new__(cls, *args, **kwargs) # 2. 如果 obj 是 cls 的实例并且类定义了 __init__则调用 __init__ if isinstance(obj, cls) and hasattr(cls, __init__): cls.__init__(obj, *args, **kwargs) # 3. 返回创建好的对象 return obj这个伪代码完美解释了之前说的所有规则__new__先被cls.__new__调用。isinstance(obj, cls)检查确保了只有__new__返回正确类型的实例时__init__才会被调用。整个流程由类的__call__方法驱动这就是为什么MyClass()可以像函数一样被调用的原因。4. 实战应用场景与代码剖析明白了原理我们来看看__new__在哪些实际场景中大放异彩。__init__的用法大家都很熟悉这里我们主要聚焦于__new__的独特用途。4.1 实现单例模式这是__new__最经典的应用。确保一个类只有一个实例。class Singleton: _instance None # 类变量用于存储唯一的实例 def __new__(cls, *args, **kwargs): # 如果还没有实例则创建一个 if cls._instance is None: print(创建新的Singleton实例) # 注意这里调用的是 object.__new__(cls)因为Singleton继承自object cls._instance super().__new__(cls) else: print(返回已存在的Singleton实例) # 无论是否新建都返回同一个_instance return cls._instance def __init__(self, value): # 注意对于单例每次调用Singleton(xx)__init__都会被调用 # 这可能导致属性被重复覆盖。需要谨慎处理。 print(fSingleton __init__ 被调用value{value}) self.value value # 测试 s1 Singleton(10) # 输出创建新的Singleton实例 \n Singleton __init__ 被调用value10 s2 Singleton(20) # 输出返回已存在的Singleton实例 \n Singleton __init__ 被调用value20 print(s1 is s2) # 输出True print(s1.value) # 输出20注意s1的value被s2的初始化覆盖了实操心得与避坑指南单例的__init__问题如上例所示即使实例是同一个每次Singleton(...)都会触发__init__。这可能会意外重置实例的状态。解决方法有几种惰性初始化在__init__中检查属性是否已存在。def __init__(self, value): if not hasattr(self, _initialized): self.value value self._initialized True使用类变量存储状态将关键数据存储在类变量而非实例变量中。放弃使用__init__改用自定义初始化方法提供get_instance()类方法来获取并可能初始化单例。线程安全上面的简单实现在多线程环境下是不安全的两个线程可能同时检查到_instance is None从而创建两个实例。在生产环境中需要使用锁threading.Lock来保护__new__方法中的创建过程。4.2 继承不可变类型当你需要自定义一个类似int或tuple的不可变类型时你必须在__new__中设置其值。class PositiveInteger(int): 一个只能表示正整数的整数类型 def __new__(cls, value): # 在创建对象之前进行验证和转换 value int(value) # 确保是整数 if value 0: raise ValueError(fPositiveInteger 必须大于0传入的值是 {value}) # 调用父类int的__new__来创建不可变对象 return super().__new__(cls, value) # __init__ 对于int这样的不可变内置类型是没用的因为对象在__new__后已定型。 # def __init__(self, value): # # 这里的代码不会影响对象的值 # pass # 测试 pos_num PositiveInteger(42) print(pos_num, type(pos_num)) # 42 class __main__.PositiveInteger print(pos_num 10) # 52仍然保持int的行为 try: PositiveInteger(-5) except ValueError as e: print(e) # PositiveInteger 必须大于0传入的值是 -5核心要点对于int,float,str,tuple,frozenset等不可变内置类型它们的值在__new__方法返回时就已经被“冻结”了。后续的__init__无法改变这个核心值。因此所有初始化和验证逻辑都必须前置到__new__中。4.3 实现对象池或缓存在某些创建成本高昂如数据库连接、网络连接、复杂计算对象的场景下我们可以使用__new__来管理一个对象池复用已创建的对象而不是每次都新建。class ExpensiveObject: 一个创建成本很高的对象 _pool {} # 用字典作为简单缓存键为创建参数值为对象 _max_pool_size 3 def __new__(cls, key): # 如果池中有直接返回 if key in cls._pool: print(f从缓存池中获取 key{key} 的对象) return cls._pool[key] # 如果池已满可以移除最旧的一个这里简单实现为不允许创建 if len(cls._pool) cls._max_pool_size: raise RuntimeError(f对象池已满最大{cls._max_pool_size}无法创建新对象) # 池中无且未满创建新对象 print(f创建新的 ExpensiveObjectkey{key}) obj super().__new__(cls) # 将新对象放入池中注意此时__init__尚未调用 cls._pool[key] obj return obj def __init__(self, key): # 注意对于从缓存取出的对象__init__依然会被调用 # 因此需要避免重复初始化。 if not hasattr(self, initialized): print(f初始化 ExpensiveObjectkey{key}) self.key key self.initialized True # 模拟昂贵的初始化操作 import time time.sleep(0.5) classmethod def clear_pool(cls): cls._pool.clear() # 测试 obj1 ExpensiveObject(A) # 创建新的... \n 初始化... obj2 ExpensiveObject(A) # 从缓存池中获取 keyA 的对象 \n 初始化... (但不会执行昂贵操作因为hasattr检查) print(obj1 is obj2) # True obj3 ExpensiveObject(B) # 创建新的... obj4 ExpensiveObject(C) # 创建新的... try: obj5 ExpensiveObject(D) # 尝试创建第四个会报错 except RuntimeError as e: print(e) # 对象池已满最大3无法创建新对象注意事项这种模式结合了单例和缓存的思想但管理起来更复杂。要特别注意线程安全、缓存失效策略如LRU以及__init__的重复调用问题。4.4 在元类编程中控制实例创建元类是类的类。__new__在元类中用于控制类的创建过程而类的__new__控制实例的创建过程。理解这一点能让你对Python的面向对象有更深的认识。class Meta(type): 一个元类用于自动为类添加一个版本号属性 def __new__(mcs, name, bases, namespace, **kwargs): print(f元类 Meta.__new__ 被调用正在创建类: {name}) # 在类被创建前修改其命名空间 namespace[__version__] 1.0.0 # 调用type.__new__来实际创建类对象 return super().__new__(mcs, name, bases, namespace) # 元类的 __call__ 方法控制了类的实例化过程 def __call__(cls, *args, **kwargs): print(f元类 Meta.__call__ 被调用即将实例化类: {cls.__name__}) # 你可以在这里对实例化过程进行全局拦截或修饰 # 例如强制所有实例化都走日志记录 instance super().__call__(*args, **kwargs) # 这会触发类的 __new__ 和 __init__ print(f元类 Meta.__call__ 完成实例创建完毕: {instance}) return instance class MyClass(metaclassMeta): def __new__(cls, value): print(fMyClass.__new__ 被调用) return super().__new__(cls) def __init__(self, value): print(fMyClass.__init__ 被调用value{value}) self.value value # 测试 print(f类 MyClass 的版本号: {MyClass.__version__}) # 1.0.0 obj MyClass(100)输出顺序清晰地展示了控制流元类 Meta.__new__ 被调用正在创建类: MyClass 类 MyClass 的版本号: 1.0.0 元类 Meta.__call__ 被调用即将实例化类: MyClass MyClass.__new__ 被调用 MyClass.__init__ 被调用value100 元类 Meta.__call__ 完成实例创建完毕: __main__.MyClass object at 0x...可以看到元类的__call__包裹了类的整个实例化过程__new____init__。这为AOP面向切面编程、类注册、ORM框架等高级功能提供了入口。5. 高级主题与疑难杂症排查在实际使用中你可能会遇到一些令人困惑的情况。下面我们来梳理并解决这些常见问题。5.1 为什么我的__init__没有被调用这是最典型的问题根本原因只有一个__new__方法没有返回当前类或其子类的实例。class ProblematicClass: def __new__(cls, x): # 错误返回了一个字符串而不是cls的实例 return fOops: {x} def __init__(self, x): print(f__init__ with {x}) # 这行永远不会执行 obj ProblematicClass(10) print(obj) # Oops: 10 print(type(obj)) # class str排查步骤检查__new__方法的返回值。确保它调用了super().__new__(cls)或object.__new__(cls)并且返回了这个结果或者基于这个结果的实例。如果你在__new__中做了条件判断如单例模式确保所有分支都返回一个正确的实例对象。5.2__new__和__init__的参数必须一致吗不一定但强烈建议保持一致。Python解释器在调用__new__和__init__时会传递相同的参数除了__new__的第一个参数是cls__init__的第一个是self。如果你定义的签名不一致会导致错误或难以理解的行为。class MismatchClass: def __new__(cls, a, b): # 接受两个参数 print(f__new__ got: a{a}, b{b}) return super().__new__(cls) def __init__(self, only_one): # 只接受一个参数 print(f__init__ got: only_one{only_one}) self.x only_one # 实例化时传递两个参数 obj MismatchClass(1, 2) # 输出 # __new__ got: a1, b2 # __init__ got: only_one1 # TypeError: __init__() missing 1 required positional argument: only_one # 实际上__init__被调用时只收到了第一个参数1第二个参数2被丢弃了但解释器期望__init__能接收两个参数所以报错。最佳实践始终使用*args, **kwargs来保持参数传递的灵活性或者在两个方法中显式定义完全相同的参数列表。5.3 在继承链中__new__和__init__的调用顺序在涉及多重继承时理解调用顺序很重要。__new__和__init__的调用都遵循Python的MRO方法解析顺序。class BaseA: def __new__(cls, *args, **kwargs): print(fBaseA.__new__) return super().__new__(cls) def __init__(self): print(fBaseA.__init__) class BaseB: def __new__(cls, *args, **kwargs): print(fBaseB.__new__) return super().__new__(cls) def __init__(self): print(fBaseB.__init__) class Derived(BaseA, BaseB): def __new__(cls, *args, **kwargs): print(fDerived.__new__) # super().__new__会调用MRO中下一个类的__new__即BaseA.__new__ return super().__new__(cls) def __init__(self): print(fDerived.__init__) super().__init__() # 调用BaseA.__init__ obj Derived()输出Derived.__new__ BaseA.__new__ BaseB.__new__ # 注意BaseB.__new__被调用了因为BaseA.__new__中的super()指向object不对这里需要仔细分析。 Derived.__init__ BaseA.__init__ BaseB.__init__实际上对于__new__super().__new__(cls)会沿着MRO找到下一个定义了__new__的类并调用。通常最终会调用到object.__new__。__init__的调用则由开发者通过super().__init__()显式控制如果不调用父类的__init__就不会执行。关键点__new__是静态方法它的super()调用与实例方法中的super()行为一致都遵循MRO。但__new__的最终目标是到达object.__new__来分配内存。5.4 如何“禁用”__init__有时你可能只想使用__new__来返回一个特定对象而不希望__init__对其做任何修改。除了让__new__返回非本类实例外更干净的做法是定义一个空的__init__或者使用元类/类装饰器来“移除”__init__。class OnlyNew: def __new__(cls, data): # 直接返回一个包装过的数据 return {wrapped_data: data, creator: cls.__name__} # 定义一个什么都不做的__init__防止因未定义__init__而可能产生的意外行为 # 实际上因为__new__返回的是dict这个__init__永远不会被调用。 # 但定义它可以让类的接口看起来更“正常”。 def __init__(self, *args, **kwargs): pass obj OnlyNew(test) print(obj) # {wrapped_data: test, creator: OnlyNew}5.5 性能考量与使用建议__new__vs__init____new__的调用频率相对较低且其性能开销主要在于对象的内存分配。__init__则可能被频繁调用尤其是在单例模式误用的情况下。对于性能极度敏感的场景需要仔细评估。何时使用__new__不要滥用__new__。仅在需要控制实例创建过程单例、不可变类型、对象池或进行非常底层的操作时才使用它。绝大多数情况下__init__足以完成初始化工作。可读性与维护性使用__new__的代码通常比只使用__init__的代码更难理解。务必添加清晰的注释说明使用__new__的原因和其特殊逻辑。6. 总结与个人经验体会回顾__new__和__init__它们的本质区别在于阶段和职责__new__是类方法负责从无到有的“构建”__init__是实例方法负责对已构建对象的“初始化”。这个区分让Python的对象模型具备了极大的灵活性。我个人在项目中最常用到__new__的场景就是实现单例和自定义不可变类型。在写一个配置管理类时单例模式能确保全局配置唯一在定义一些值对象Value Object时通过继承tuple并在__new__中处理数据能获得不可变性和性能优势。一个容易忽略的细节是__init__并不是“构造方法”Python中没有真正的构造方法。__new__才是。正因为如此你才能在__new__里“为所欲为”。理解这一点很多关于Python对象创建的困惑就迎刃而解了。最后一个小技巧当你对实例化过程有疑问时除了加print调试不妨写一个简单的元类重写它的__call__方法在里面打印日志。这能帮你清晰地看到从类调用到实例诞生的完整链条是深入学习Python对象模型的神器。