0

0

深入理解Python中__new__与__init__的执行机制及常见陷阱

霞舞

霞舞

发布时间:2025-08-06 23:02:01

|

939人浏览过

|

来源于php中文网

原创

深入理解Python中__new__与__init__的执行机制及常见陷阱

Python中__new__和__init__是对象创建和初始化的两个关键方法。__new__负责实例的创建,而__init__负责实例的初始化。文章将深入探讨它们的调用顺序、在继承链中的行为,以及__new__的正确使用方式,特别是避免在__new__中手动调用__init__或返回非实例对象所导致的常见陷阱,帮助开发者构建健壮的Python类。

__new__与__init__的基本概念

python中,对象的生命周期通常涉及两个特殊方法:

  1. *`new(cls, args, kwargs)`:

    • 这是一个类方法(即使没有使用@classmethod装饰器,它也隐式地接收类作为第一个参数cls)。
    • 它在实例被创建之前调用,负责创建并返回一个类的实例。
    • 它是真正创建实例的方法,通常通过调用父类的__new__方法(如super().__new__(cls)或object.__new__(cls))来完成实例的创建。
    • 如果__new__没有返回当前类的实例(或者返回None),那么该类的__init__方法将不会被调用。
  2. *`init(self, args, kwargs)`:

    • 这是一个实例方法,它在__new__方法返回实例后立即调用。
    • 它负责初始化新创建实例的状态,例如设置实例的属性。
    • 它不返回任何值(隐式返回None)。

调用顺序总结: 当你创建一个类的实例时(例如obj = MyClass()),Python解释器会首先调用MyClass.__new__()来创建实例。如果__new__成功创建并返回了一个MyClass的实例,那么解释器会接着调用该实例的MyClass.__init__()方法来对其进行初始化。

__new__的正确使用姿势

__new__通常只在需要控制实例创建过程的特殊场景下重写,例如:

  • 单例模式(Singleton):确保一个类只有一个实例。
  • 不可变对象(Immutable Objects):在实例创建时进行特殊处理。
  • 元类(Metaclasses):高级用法,用于自定义类的创建。

在重写__new__时,务必遵循以下原则:

立即学习Python免费学习笔记(深入)”;

  1. 第一个参数必须是cls:即使它是一个实例方法,__new__在被调用时,第一个参数永远是当前类。
  2. 必须返回一个实例:通常是通过调用父类的__new__方法来获取实例,例如 instance = super().__new__(cls, *args, **kwargs)。
  3. 不要在__new__中手动调用__init__:这是Python解释器的工作。如果在__new__中手动调用__init__,可能会导致重复初始化、初始化未完全创建的对象,或在__new__返回非实例对象时引发错误。

继承链中的行为

当存在继承关系时,__new__和__init__的调用行为会更加复杂:

  • __new__的调用:当你实例化一个子类时,子类的__new__方法会首先被调用。如果子类没有重写__new__,则会向上查找父类的__new__方法。
  • __init__的调用:只有当__new__方法返回的是当前类或其子类的实例时,该实例的__init__方法才会被调用。如果__new__返回了其他类型的对象,或者返回了None,那么当前类的__init__将不会被执行。

案例分析与常见陷阱

我们通过一个示例来深入理解__new__和__init__的复杂交互,特别是当__new__被错误实现时。

考虑以下有问题的代码:

class Demo:
    def __new__(self): # 错误:__new__应接收cls作为第一个参数
        self.__init__(self) # 陷阱:在__new__中手动调用__init__
        print("Demo's __new__() invoked")
        # 隐式返回None,因为没有显式return
    def __init__(self):
        print("Demo's __init__() invoked")

class Derived_Demo(Demo):
    def __new__(self): # 错误:__new__应接收cls作为第一个参数
        print("Derived_Demo's __new__() invoked")
        # 隐式返回None,因为没有显式return
    def __init__(self):
        print("Derived_Demo's __init__() invoked")

def main():
    obj1 = Derived_Demo()
    print(f"obj1 is: {obj1}") # 观察obj1的值
    obj2 = Demo()
    print(f"obj2 is: {obj2}") # 观察obj2的值

main()

运行上述代码的输出:

Derived_Demo's __new__() invoked
Demo's __init__() invoked
Demo's __new__() invoked
obj1 is: None
obj2 is: None

输出分析:

Prisms AI
Prisms AI

无代码构建AI应用的平台

下载
  1. obj1 = Derived_Demo() 调用 Derived_Demo.__new__():

    • 打印 "Derived_Demo's __new__() invoked"。
    • 陷阱一:Derived_Demo.__new__方法没有显式返回任何对象。在Python中,如果一个函数没有显式return语句,它会隐式返回None。因此,obj1最终被赋值为None。
    • 陷阱二:由于__new__返回了None而不是Derived_Demo的实例,Python解释器不会继续调用Derived_Demo.__init__。这就是为什么你没有看到 "Derived_Demo's __init__() invoked"。
  2. obj2 = Demo() 调用 Demo.__new__():

    • 陷阱三:在Demo.__new__内部,它手动调用了self.__init__(self)。此时,self实际上是Demo类(因为__new__的第一个参数是类)。这个调用导致了Demo.__init__被执行,打印出 "Demo's __init__() invoked"。这是一个反模式,因为__init__应该由Python解释器在__new__返回实例后自动调用。
    • 接着,打印 "Demo's __new__() invoked"。
    • 陷阱四:与Derived_Demo.__new__类似,Demo.__new__也没有显式返回任何对象,因此它也隐式返回None。obj2最终也被赋值为None。

正确的__new__和__init__实现

为了避免上述问题,并确保__new__和__init__按预期工作,我们应该这样实现它们:

class Demo:
    def __new__(cls, *args, **kwargs): # 正确:第一个参数是cls
        print("Demo's __new__() invoked")
        # 正确:通过super()调用父类(object)的__new__来创建实例
        instance = super().__new__(cls)
        return instance # 正确:返回创建的实例

    def __init__(self, value=None): # 可以有参数,用于初始化实例
        print(f"Demo's __init__() invoked with value: {value}")
        self.value = value

class Derived_Demo(Demo):
    def __new__(cls, *args, **kwargs): # 正确:第一个参数是cls
        print("Derived_Demo's __new__() invoked")
        # 正确:通过super()调用父类(Demo)的__new__来创建实例
        instance = super().__new__(cls)
        return instance # 正确:返回创建的实例

    def __init__(self, name=None): # 可以有参数,用于初始化实例
        super().__init__(name) # 调用父类的__init__
        print(f"Derived_Demo's __init__() invoked with name: {name}")
        self.name = name

def main():
    print("--- Creating obj1 ---")
    obj1 = Derived_Demo(name="Alice")
    print(f"obj1 is: {obj1}")
    print(f"obj1.name: {obj1.name}")
    print(f"obj1.value: {obj1.value}") # 继承自Demo的属性

    print("\n--- Creating obj2 ---")
    obj2 = Demo(value=123)
    print(f"obj2 is: {obj2}")
    print(f"obj2.value: {obj2.value}")

main()

运行正确代码的输出:

--- Creating obj1 ---
Derived_Demo's __new__() invoked
Demo's __new__() invoked
Demo's __init__() invoked with value: Alice
Derived_Demo's __init__() invoked with name: Alice
obj1 is: <__main__.Derived_Demo object at 0x...>
obj1.name: Alice
obj1.value: Alice

--- Creating obj2 ---
Demo's __new__() invoked
Demo's __init__() invoked with value: 123
obj2 is: <__main__.Demo object at 0x...>
obj2.value: 123

正确代码分析:

  1. obj1 = Derived_Demo(name="Alice"):

    • 首先调用Derived_Demo.__new__(cls, name="Alice")。
    • Derived_Demo.__new__内部调用super().__new__(cls),这会继续向上调用Demo.__new__,最终由object.__new__(cls)创建Derived_Demo的实例。
    • Derived_Demo.__new__返回这个实例。
    • Python解释器接收到这个实例后,接着调用Derived_Demo.__init__(obj1, name="Alice")。
    • Derived_Demo.__init__内部调用super().__init__(name),这会调用Demo.__init__来初始化父类部分。
    • 最终,obj1是一个完全初始化好的Derived_Demo实例。
  2. obj2 = Demo(value=123):

    • 首先调用Demo.__new__(cls, value=123)。
    • Demo.__new__内部调用super().__new__(cls),由object.__new__(cls)创建Demo的实例。
    • Demo.__new__返回这个实例。
    • Python解释器接收到这个实例后,接着调用Demo.__init__(obj2, value=123)。
    • 最终,obj2是一个完全初始化好的Demo实例。

总结与最佳实践

  • __new__是工厂,__init__是装修工:__new__负责生产出毛坯房(实例),__init__负责装修和配置这个毛坯房。
  • 谨慎重写__new__:除非你有特定的需求(如实现单例模式、自定义不可变对象等),否则通常不需要重写__new__方法。
  • 确保__new__返回实例:如果你重写了__new__,它必须返回一个有效的实例(通常是super().__new__(cls, ...)的结果)。如果返回None或非当前类的实例,后续的__init__将不会被调用。
  • 避免在__new__中手动调用__init__:这是Python解释器的工作。手动调用会导致不确定行为,并且可能绕过正常的初始化流程。
  • __new__的参数传递:__new__方法接收的参数(除了cls之外)会原封不动地传递给__init__。因此,__new__和__init__的签名(除了第一个参数)通常是匹配的。

理解__new__和__init__的正确用法对于编写健壮和高效的Python代码至关重要,尤其是在处理复杂的类结构和对象创建逻辑时。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

749

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

635

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

758

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

618

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1262

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

577

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

705

2023.08.11

PHP 表单处理与文件上传安全实战
PHP 表单处理与文件上传安全实战

本专题聚焦 PHP 在表单处理与文件上传场景中的实战与安全问题,系统讲解表单数据获取与校验、XSS 与 CSRF 防护、文件类型与大小限制、上传目录安全配置、恶意文件识别以及常见安全漏洞的防范策略。通过贴近真实业务的案例,帮助学习者掌握 安全、规范地处理用户输入与文件上传的完整开发流程。

1

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 3万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号