Python可变与不可变对象深度解析:内存机制与参数传递

山间有风
山间有风
发布时间:2026-10-14| 4人浏览过
原创

本文深入解析Python中可变(如list、dict)与不可变(如int、string、tuple)对象的内存行为差异。通过代码示例说明变量赋值、修改时的内存地址变化,并详细阐述Python函数参数传递机制:尽管采用引用传递,但可变对象修改会影响外部,而不可变对象因创建新对象而不影响外部。最后介绍基于引用计数的自动垃圾回收机制,帮助开发者理解Python内存管理逻辑。

Python可变与不可变对象概述

在Python语言中,对象被划分为可变和不可变两类。常见的可变对象包括list(列表)和dict(字典),而int(整数)、string(字符串)、float(浮点数)以及tuple(元组)则属于不可变对象。

不可变对象的内存行为

理解Python中的不可变对象特性是掌握内存管理的基础。


int,string,float,tuple

首先通过一个具体示例来观察这一现象。


def int_test(): 
 i = 77
 j = 77
 print(id(77))     #140396579590760
 print('i id:' + str(id(i)))  #i id:140396579590760
 print('j id:' + str(id(j)))  #j id:140396579590760
 print i is j     #True
 j = j + 1
 print('new i id:' + str(id(i))) #new i id:140396579590760
 print('new j id:' + str(id(j))) #new j id:140396579590736
 print i is j     #False
 
if __name__ == '__main__':
 int_test()

假设存在两个变量i和j,它们的值均为77。通过打印整数77的ID以及变量i和j在内存中的ID,可以发现它们指向同一块内存地址,这意味着i和j指向的是同一个对象。随后,我们将j的值加1。按照直觉,既然i和j指向同一内存,修改j后i的值似乎也应改变,但实际结果并非如此。这是因为int类型是不可变类型,当对j进行+1操作时,Python会在新的内存地址复制一份数据并加1,然后让j指向这个新地址。因此,j的内存ID发生了变化,而i仍指向原地址。

此时的内存分配情况如下所示:

再次观察变量i和j值为77的情况。打印77的ID及变量i、j的内存ID,确认它们指向同一块内存,即指向同一对象。接着将j的值加1。理论上,若i和j共享同一内存,修改j应影响i,但结果并未如此。原因在于int类型是不可变的,修改j时,Python实际上是将数据复制到新的内存地址进行+1运算,随后让j指向新地址。因此,j的内存ID发生改变,而i保持不变。

内存变化的具体分布如下:


def dict_test():
 a = {}
 b = a
 print(id(a))
 a['a'] = 'hhhh'
 print('id a:' + str(id(a)))
 print('a:' + str(a))
 print('id b:' + str(id(b)))
 print('b:' + str(b))if __name__ == '__main__':
 dict_test()

代码运行结果如下:

140367329543360 id a:140367329543360 a:{'a': 'hhhh'} id b:140367329543360 b:{'a': 'hhhh'}

从输出可见,变量a最初的内存地址ID为140367329543360。将a赋值给b,实际上是让变量b也指向a所指向的内存空间。当a的内容发生变化后,b也随之改变。这是因为list是可变类型,修改时不会复制新对象,而是直接在a指向的内存空间中修改数据。由于b也指向该内存空间,因此b的内容自然随之改变。

内存变化示意图如下:

函数参数传递机制

接下来探讨Python函数的参数传递机制。

Python规定参数传递均传递引用,即传递给函数的是原变量实际指向的内存空间。在修改操作时,函数会根据该引用指向去修改内存中的内容。因此,理论上如果在函数内部修改了传递过来的参数值,外部的变量也应受到影响。

然而,前文提到的Python存在可变和不可变类型的区别。当传入的是可变类型(如list、dict)时,在函数内部修改参数会影响函数外部的变量。而当传入的是不可变类型时,在函数内部修改变量并不会影响外部变量,因为修改操作会先复制一份新对象再进行修改。下面通过代码验证这一结论:


def test(a_int, b_list):
 a_int = a_int + 1
 b_list.append('13')
 print('inner a_int:' + str(a_int))
 print('inner b_list:' + str(b_list))
if __name__ == '__main__':
 a_int = 5
 b_list = [10, 11]
 test(a_int, b_list)
 print('outer a_int:' + str(a_int))
 print('outer b_list:' + str(b_list))

运行结果如下:


inner a_int:6
inner b_list:[10, 11, '13']
outer a_int:5
outer b_list:[10, 11, '13']

答案显而易见。经过test()方法修改后,传递进来的int类型外部变量没有发生改变,而list这种可变类型则因为test()方法的影响导致内容发生了改变。

总结与内存管理原理

总结上述内容:

在许多其他编程语言中,程序员可以选择值传递或引用传递(例如C语言中通过*号传递指针即为引用传递,直接传递变量名则为值传递)。而Python只允许使用引用传递,但结合可变类型和不可变类型的概念,使得行为显得较为复杂。据称,Python采用引用传递是为了方便内存管理,因为Python使用的内存回收机制是计数器回收。具体而言,每块内存上有一个计数器,表示当前有多少个对象指向该内存。每当一个变量不再使用时,计数器减1;当有新对象指向该内存时,计数器加1。当计数器为0时,系统即可回收这块内存。

知识点扩展:

原理深度解析

Python可变对象与不可变对象原理解析

原理

可变对象包括:list、dict、set。

不可变对象包括:tuple、string、int、float、bool。

1. Python不允许程序员选择采用传值还是传引用,其参数传递采用的是“传对象引用”方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象的引用,就能修改对象的原始值——这相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象的引用,就不能直接修改原始对象——这相当于通过“传值”来传递对象。

2. 当人们复制可变对象时,实际上复制的是可变对象的引用。如果改变引用的值,则修改了原始的参数。

3. 为了简化内存管理,Python通过引用计数机制实现自动垃圾回收功能。Python中的每个对象都有一个引用计数,用来计数该对象在不同场所分别被引用了多少次。每当引用一次Python对象,相应的引用计数就增1;每当销毁一次Python对象,则相应的引用就减1。只有当引用计数为零时,才真正从内存中删除Python对象。

到此这篇关于python新手学习可变和不可变对象的文章就介绍到这了,更多相关python可变对象和不可变对象内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。