Python中可变对象与不可变对象详细教程

 更新时间:2026年06月03日 09:42:34   作者:登山人在路上  
Python中,可变数据类型指的是数据值可以改变的数据类型,而不可变数据类型指的是数据值不可改变的数据类型,这篇文章主要介绍了Python中可变对象与不可变对象的相关资料,需要的朋友可以参考下

前言

我们来深入解析Python中一个核心概念:可变(Mutable)对象不可变(Immutable)对象。这个概念直接关系到你的代码在内存中如何运作,尤其是在进行赋值、拷贝、传参等操作时,理解它可以帮助你避免许多难以发现的bug。

核心定义

判断一个对象是可变的还是不可变的,标准只有一个:当对象创建之后,它里面的值能否被改变?

  1. 不可变对象:对象创建后,其内容(或状态)无法被修改。如果你尝试修改它,Python实际上会创建一个新的对象,而不是在原有对象上修改。

  2. 可变对象:对象创建后,其内容(或状态)可以被修改。你可以在原内存地址上直接修改对象的值,而不会创建新对象。

1. 常见的不可变对象

  • 整数(int)

  • 浮点数(float)

  • 布尔值(bool)

  • 字符串(str)

  • 元组(tuple) —— 注意:元组本身不可变,但如果它包含可变对象(如列表),那个内部对象是可变的。

  • 冻结集合(frozenset)

示例:看似"修改"了不可变对象

name = "hello"
print(f"原始对象的内存地址: {id(name)}")  # id() 可以查看对象的内存地址

name = name + " world"
print(f"新对象的内存地址: {id(name)}")

# 输出:
# 原始对象的内存地址: 1399876543210  (示例地址)
# 新对象的内存地址: 1399876545678  (地址变了!)

发生了什么?

当执行 name = name + " world" 时,Python并没有在原来的字符串 "hello" 后面添加内容。因为字符串是不可变的,它不能直接在原处修改。所以,Python在内存中创建了一个全新的字符串对象 "hello world",然后将变量 name 重新指向了这个新对象。原来的 "hello" 对象如果没有其他变量引用,最终会被垃圾回收。

2. 常见的可变对象

  • 列表(list)

  • 字典(dict)

  • 集合(set)

  • 自定义类的实例对象(默认情况下)

示例:修改可变对象

my_list = [1, 2, 3]
print(f"修改前列表的内存地址: {id(my_list)}")

my_list.append(4)  # 在列表末尾添加一个元素
print(f"修改后列表的内容: {my_list}")
print(f"修改后列表的内存地址: {id(my_list)}")

# 输出:
# 修改前列表的内存地址: 1399876547890  (示例地址)
# 修改后列表的内容: [1, 2, 3, 4]
# 修改后列表的内存地址: 1399876547890  (地址没变!)

示例2:

# 创建一个可变对象(列表)
a = [1, 2, 3]      # a 指向一个列表对象 [1, 2, 3]
print(f"初始状态:")
print(f"a = {a}, id(a) = {id(a)}")

b = a              # b 指向同一个列表对象
print(f"\n执行 b = a 后:")
print(f"a = {a}, id(a) = {id(a)}")
print(f"b = {b}, id(b) = {id(b)}")
print(f"a is b? {a is b}")  # True,确实是同一个对象

# 情况1:通过 a 修改列表内容(不是重新赋值)
a.append(4)        # 修改可变对象的内容
print(f"\n执行 a.append(4) 后:")
print(f"a = {a}, id(a) = {id(a)}")
print(f"b = {b}, id(b) = {id(b)}")
print(f"b 看到了变化!因为 a 和 b 指向同一个列表")

# 情况2:重新给 a 赋值(让 a 指向新对象)
a = [5, 6, 7]      # a 现在指向一个新列表
print(f"\n执行 a = [5, 6, 7] 后:")
print(f"a = {a}, id(a) = {id(a)}")
print(f"b = {b}, id(b) = {id(b)}")
print(f"b 仍然指向原来的列表,不受 a 重新赋值的影响")

输出:

初始状态:
a = [1, 2, 3], id(a) = 1399876543210

执行 b = a 后:
a = [1, 2, 3], id(a) = 1399876543210
b = [1, 2, 3], id(b) = 1399876543210
a is b? True

执行 a.append(4) 后:
a = [1, 2, 3, 4], id(a) = 1399876543210
b = [1, 2, 3, 4], id(b) = 1399876543210
b 看到了变化!因为 a 和 b 指向同一个列表

执行 a = [5, 6, 7] 后:
a = [5, 6, 7], id(a) = 1399876545678
b = [1, 2, 3, 4], id(b) = 1399876543210
b 仍然指向原来的列表,不受 a 重新赋值的影响

发生了什么?

当我们调用 my_list.append(4) 时,Python直接在原有的列表对象上进行了修改。它没有创建新的列表,只是改变了原列表的内部状态。因此,列表的内存地址在修改前后是完全相同的。

3. 为什么这个区别很重要?

理解可变与不可变,对于编写正确的代码至关重要,尤其是在以下场景:

a) 变量赋值和引用

内存示意图:

栈内存:
a: [10]  →  a: [20]  (a的值被直接修改)
b: [10]      b: [10]  (b独立存储,不受影响)

2. 对于引用类型(Reference Types)

// 假设有一个 Person 类
Person a = new Person("Alice");  // a 存储的是对象的引用(内存地址)
Person b = a;                    // 把引用(地址)复制给 b
a.setName("Bob");                 // 通过 a 修改对象内容
System.out.println(b.getName());  // 输出: "Bob" (b 指向同一个对象)

a = new Person("Charlie");        // a 指向一个新对象
System.out.println(b.getName());  // 输出: "Bob" (b 仍然指向原来的对象)

b) 函数参数的传递

Python的参数传递方式是"对象引用传递"。这意味着函数内部接收的是指向实际对象的引用。

c) 作为字典的键

4. 特殊情况:元组中包含可变对象

这是一个需要特别注意的地方。元组本身是不可变的,这意味着你不能给元组添加或删除元素,也不能替换元组中的某个元素。但是,如果元组中包含了可变对象(如列表),那么这个可变对象的内容是可以被修改的

t = (1, 2, [3, 4])  # 元组中包含一个列表
print(t)  # 输出: (1, 2, [3, 4])

# t[0] = 10      # 错误!不能替换元组的元素,会报 TypeError
# t.append(5)    # 错误!元组没有 append 方法

t[2][0] = 999   # 可以修改元组内部列表的元素
print(t)  # 输出: (1, 2, [999, 4])  (元组的内容看起来变了!)

解释:

虽然元组存储的引用本身不能变,但它并没有限制这些引用所指向的对象内部是否能变。这里,元组中第三个槽位始终指向同一个列表对象。我们只是修改了这个列表对象的内容,并没有改变元组中存储的引用。所以从元组的角度看,它依然是"不可变"的。

总结

特性不可变对象可变对象
常见类型intfloatstrtuplefrozensetlistdictset, 自定义类实例
修改操作任何修改都会创建一个新对象可以在原内存地址上直接修改内容
内存地址修改后内存地址会改变修改后内存地址通常不变
安全性多个引用共享时安全,不会被意外修改多个引用共享时需谨慎,一处修改处处可见
作为字典键可以不可以
函数传参函数内修改不会影响外部变量函数内修改会影响外部对象

理解这个核心区别,是掌握Python内存模型和写出健壮代码的关键一步。

到此这篇关于Python中可变对象与不可变对象详细教程的文章就介绍到这了,更多相关Python可变对象与不可变对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python各类经纬度转换的实例代码

    python各类经纬度转换的实例代码

    这篇文章主要介绍了python各类经纬度转换的实例代码,非常不错,具有一定的参考借鉴价值,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • Python进度条可视化之监测程序运行速度

    Python进度条可视化之监测程序运行速度

    Tqdm是一个快速,可扩展的Python进度条,可以在Python长循环中添加一个进度提示信息,用户只需要封装任意的迭代器即可。本文就主要介绍了通过进度条检测程序运行速度,感兴趣的同学可以学习一下
    2021-12-12
  • Python中类的继承代码实例

    Python中类的继承代码实例

    这篇文章主要介绍了Python中类的继承代码实例,本文直接给出代码及运行效果,需要的朋友可以参考下
    2014-10-10
  • Django中的session用法详解

    Django中的session用法详解

    这篇文章主要介绍了Django中的session用法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • Python实现Smtplib发送带有各种附件的邮件实例

    Python实现Smtplib发送带有各种附件的邮件实例

    本篇文章主要介绍了Python实现Smtplib发送带有各种附件的邮件实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • 利用Python+requests批量检测网站是否可访问的实战指南

    利用Python+requests批量检测网站是否可访问的实战指南

    在日常开发、运维、测试和数据采集中,我们经常需要判断一批网站或接口是否能够正常访问,如果一个一个用浏览器打开,不仅效率低,而且很难记录结果,使用 Python 的 requests 库,就可以把这个过程自动化,本文会用一个完整案例演示,需要的朋友可以参考下
    2026-05-05
  • 一文详解5个你可能不知道但非常有用的Python库

    一文详解5个你可能不知道但非常有用的Python库

    Python作为当今最流行的编程语言之一,其强大的生态系统是其成功的关键因素,在这个庞大的生态系统中,除了众所周知的NumPy、Pandas、Requests等明星库外,还隐藏着许多功能强大但鲜为人知的瑰宝,本文呢就为大家盘点了一些你可能不知道但非常有用的Python库
    2025-11-11
  • VScode连接远程服务器上的jupyter notebook的实现

    VScode连接远程服务器上的jupyter notebook的实现

    这篇文章主要介绍了VScode连接远程服务器上的jupyter notebook的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • Python中如何导入类示例详解

    Python中如何导入类示例详解

    这篇文章主要给大家介绍了关于Python中如何导入类的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Python具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-04-04
  • python GUI库图形界面开发之PyQt5表单布局控件QFormLayout详细使用方法与实例

    python GUI库图形界面开发之PyQt5表单布局控件QFormLayout详细使用方法与实例

    这篇文章主要介绍了python GUI库图形界面开发之PyQt5布局控件QFormLayout详细使用方法与实例,需要的朋友可以参考下
    2020-03-03

最新评论