讲师中心 微信公众号
AI工具推荐 视频效率加速

Python中的可变对象和不可变对象有哪些?区别是什么?

夏伟同学_1375

夏伟同学_1375

发布时间:2025-09-06 13:58:02

|

445人浏览过

|

来源于php中文网

原创

Python中对象分为可变和不可变两类,区别在于创建后能否修改其内容。可变对象(如列表、字典、集合)允许原地修改,内存地址不变;不可变对象(如整数、字符串、元组)一旦创建内容不可更改,任何“修改”实际是创建新对象。这种机制影响函数参数传递、哈希性、并发安全和性能优化。例如,不可变对象可作为字典键,因具有固定哈希值;而可变对象则不能。判断可变性可通过尝试修改并观察id()是否变化,或使用hash()检查是否可哈希。理解此差异有助于避免副作用和潜在bug,提升代码可预测性与健壮性。

python中的可变对象和不可变对象有哪些?区别是什么?

Python中的对象大致可以分为两类:可变对象和不可变对象。核心区别在于,可变对象在创建后其内部状态可以被修改,而不可变对象一旦创建,其值就不能再改变。如果看起来它们“变”了,那通常是创建了一个新的对象。理解这一点,对于写出健壮、可预测的Python代码至关重要。

解决方案

在Python的世界里,对象的“可变性”(mutability)和“不可变性”(immutability)是理解其工作机制的关键概念。简单来说,一个可变对象就像一块橡皮泥,你可以随意捏扁搓圆,改变它的形状,但它依然是那块橡皮泥,内存地址不变。而不可变对象则更像是一块雕塑,一旦完成,你就不能再改变它。任何对它的“修改”操作,实际上都是在旁边重新雕刻了一块新的雕塑。

这种设计哲学渗透在Python的方方面面。比如,当你有一个列表(list),你可以直接在原地添加元素、删除元素,甚至修改特定位置的值,这些操作都不会改变列表本身的内存地址。但如果你有一个字符串(string),尝试修改它的某个字符,Python会直接报错,因为字符串是不可变的。所有看似修改字符串的操作,比如字符串拼接,实际上都是创建了一个全新的字符串对象。

这种区分带来的影响非常深远,尤其是在函数参数传递、多线程编程(虽然Python的GIL在一定程度上简化了并发数据修改的问题,但理解可变性依然重要)、以及作为字典键或集合元素时。我个人觉得,很多初学者在遇到意料之外的行为时,往往就是因为没有搞清楚对象的可变性。

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

Python中常见的可变对象类型有哪些?

谈到可变对象,我们最常打交道的莫过于列表(

list
)、字典(
dict
)和集合(
set
)了。它们是Python数据结构中的主力军,也是我们日常编程中修改频率最高的几种。

列表(

list
)是我觉得最直观的可变对象。你可以直接在原地增删改查。比如,
my_list = [1, 2, 3]
,然后
my_list.append(4)
,你会发现
my_list
变成了
[1, 2, 3, 4]
,而
id(my_list)
并没有变。这种“原地修改”的特性让列表在处理动态数据集时非常高效。

my_list = [1, 2, 3]
print(f"原始列表: {my_list}, ID: {id(my_list)}")

my_list.append(4) # 在原地修改
print(f"添加元素后: {my_list}, ID: {id(my_list)}")

my_list[0] = 10 # 修改第一个元素
print(f"修改元素后: {my_list}, ID: {id(my_list)}")

字典(

dict
)也一样,它是键值对的集合。你可以随时添加新的键值对,修改现有键对应的值,或者删除键值对,而字典对象本身依然是那个字典。

my_dict = {'a': 1, 'b': 2}
print(f"原始字典: {my_dict}, ID: {id(my_dict)}")

my_dict['c'] = 3 # 添加新键值对
print(f"添加元素后: {my_dict}, ID: {id(my_dict)}")

my_dict['a'] = 10 # 修改值
print(f"修改值后: {my_dict}, ID: {id(my_dict)}")

集合(

set
)是无序不重复元素的集合,它也支持添加和删除元素的操作。

my_set = {1, 2, 3}
print(f"原始集合: {my_set}, ID: {id(my_set)}")

my_set.add(4) # 添加元素
print(f"添加元素后: {my_set}, ID: {id(my_set)}")

my_set.remove(1) # 删除元素
print(f"删除元素后: {my_set}, ID: {id(my_set)}")

此外,像

bytearray
也是可变类型。自定义的类实例,除非你特别设计成不可变,否则它们通常也是可变的,因为你可以随时修改它们的属性。理解这些,在构建复杂数据结构时,能避免很多坑。

Python中常见的不可变对象类型有哪些?

与可变对象相对,不可变对象一旦创建,其值就无法改变。这听起来可能有点限制,但实际上,这种特性带来了稳定性和可预测性。最典型的不可变对象包括数字(

int
,
float
,
complex
)、字符串(
str
)、元组(
tuple
)和冻结集合(
frozenset
)。

数字类型,例如整数和浮点数,是不可变的。当你写

x = 5
,然后
x = x + 1
时,你并不是改变了
5
这个值本身,而是创建了一个新的整数对象
6
,然后让变量
x
指向了它。

x = 5
print(f"原始整数: {x}, ID: {id(x)}")

x = x + 1 # 实际上是创建了一个新的整数对象 6
print(f"加1后: {x}, ID: {id(x)}") # ID会改变

字符串(

str
)是另一个非常重要的不可变类型。在我看来,字符串的不可变性是Python设计中一个非常优雅的地方。它使得字符串操作更安全,尤其是在多线程环境下,你不用担心某个线程意外修改了另一个线程正在使用的字符串。当你进行字符串拼接时,比如
s = "hello" + " world"
,实际上是创建了一个新的字符串
"hello world"
。

macOS UI 自动化技能包 —— 用 Python 控制你的 Mac
macOS UI 自动化技能包 —— 用 Python 控制你的 Mac

让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限

下载
s = "hello"
print(f"原始字符串: {s}, ID: {id(s)}")

s += " world" # 实际上是创建了一个新的字符串对象
print(f"拼接后: {s}, ID: {id(s)}") # ID会改变

元组(

tuple
)是不可变序列。一旦创建,你不能添加、删除或修改其内部元素。如果元组中包含可变对象,比如一个列表,那么这个列表本身还是可以被修改的,但元组中指向这个列表的引用是不可变的。这有点像一个只读的指针,指向的内容可以变,但指针本身不能指向别的地方。

my_tuple = (1, 2, [3, 4])
print(f"原始元组: {my_tuple}, ID: {id(my_tuple)}")

# my_tuple[0] = 10 # 这会报错,因为元组不可变
# print(f"尝试修改元素: {my_tuple}, ID: {id(my_tuple)}")

my_tuple[2].append(5) # 元组中的列表是可变的,所以可以修改
print(f"修改元组内列表后: {my_tuple}, ID: {id(my_tuple)}") # 元组ID不变

冻结集合(

frozenset
)是不可变的集合版本,可以作为字典的键。字节串(
bytes
)也是不可变的。理解这些,有助于我们更好地选择数据结构,并预测代码的行为。

为什么Python要区分可变与不可变对象?

Python区分可变与不可变对象,绝不是为了增加复杂性,而是出于多方面的设计考量,这些考量最终提升了语言的实用性、安全性和性能。我个人认为,这体现了Python在灵活性和健壮性之间寻求平衡的智慧。

首先,哈希性(Hashability) 是一个非常重要的原因。不可变对象通常是“可哈希的”(hashable),这意味着它们有一个固定的哈希值,这个哈希值在对象的生命周期内不会改变。这使得不可变对象能够被用作字典的键(

dict
keys)和集合的元素(
set
elements)。想象一下,如果一个字典的键是可变的,那么键的值改变了,它在哈希表中的位置也会变,这会彻底破坏字典的查找机制。而可变对象(如列表、字典)因为其内容可能变化,所以默认是不可哈希的,不能直接作为字典键或集合元素。

其次,数据完整性和安全性。不可变对象一旦创建就不能被修改,这在很多场景下非常有用。比如,当你将一个不可变对象传递给函数时,你可以确信函数不会意外地修改原始对象。这减少了副作用(side effects)的发生,使得代码更容易理解和调试。在并发编程中,虽然Python的GIL(全局解释器锁)限制了多线程的并行执行,但在多进程或异步编程中,不可变数据结构能有效避免竞态条件(race conditions),因为它们本身就是线程安全的。

再者,性能优化。Python解释器可以对不可变对象进行一些内部优化。例如,对于小整数(-5到256)和某些短字符串,Python会进行“interning”(驻留),即在内存中只保留一份副本。当你创建多个相同值的不可变对象时,它们可能都指向内存中的同一个对象,这节省了内存。

a = 1
b = 1
print(id(a) == id(b)) # True,因为小整数被驻留

s1 = "hello"
s2 = "hello"
print(id(s1) == id(s2)) # True,短字符串也被驻留

l1 = [1, 2]
l2 = [1, 2]
print(id(l1) == id(l2)) # False,列表是可变的,即使内容相同也是不同对象

最后,函数式编程范式。不可变性是函数式编程的核心原则之一。它鼓励我们编写没有副作用的函数,即函数只接收输入,产生输出,而不修改任何外部状态。这使得代码更模块化,更容易测试和组合。虽然Python不是纯粹的函数式语言,但不可变对象的存在,无疑为追求这种风格的开发者提供了便利。

在我看来,这种区分是Python设计哲学的一个缩影:提供强大的工具,同时也要求开发者理解这些工具的特性,以便更好地驾驭它们。忽略可变性与不可变性的差异,可能会导致一些难以追踪的bug,比如在默认参数中使用可变对象。

如何判断一个对象是可变还是不可变?

在Python中,判断一个对象是可变还是不可变,通常有几种方法,有些是经验性的,有些则是通过内置函数进行验证。这不是一个“一刀切”的检查,更像是结合对Python数据类型的理解和一些小技巧。

最直接的方法,也是我个人最常用的,就是尝试修改它。 如果你能成功地在原地修改对象的内容,并且

id()
函数返回的内存地址保持不变,那么它就是可变的。

my_list = [1, 2]
print(f"修改前ID: {id(my_list)}")
my_list.append(3)
print(f"修改后ID: {id(my_list)}") # ID通常不变,列表可变

如果尝试修改会抛出错误(比如

TypeError: 'str' object does not support item assignment
),或者修改操作实际上是返回了一个新对象(导致
id()
发生变化),那么它就是不可变的。

my_string = "hello"
print(f"修改前ID: {id(my_string)}")
# my_string[0] = 'H' # 这会报错
my_string = my_string + " world" # 这会创建新对象
print(f"修改后ID: {id(my_string)}") # ID会变,字符串不可变

另一个非常重要的判断依据是哈希性(Hashability)。可哈希的对象通常是不可变的。你可以使用内置的

hash()
函数来检查一个对象是否可哈希。如果
hash()
函数能成功返回一个整数值,那么这个对象就是可哈希的,通常意味着它是不可变的(自定义类可以通过实现
__hash__
和
__eq__
方法来变得可哈希)。如果
hash()
抛出
TypeError
,那么这个对象就是不可哈希的,通常是可变的。

print(f"整数1的哈希值: {hash(1)}") # 可哈希
print(f"字符串'abc'的哈希值: {hash('abc')}") # 可哈希
print(f"元组(1, 2)的哈希值: {hash((1, 2))}") # 可哈希

try:
    hash([1, 2])
except TypeError as e:
    print(f"列表不可哈希: {e}") # 列表不可哈希
try:
    hash({'a': 1})
except TypeError as e:
    print(f"字典不可哈希: {e}") # 字典不可哈希

最后,查阅Python文档或经验。对于内置类型,记住哪些是可变、哪些是不可变,是最直接有效的方法。比如:

  • 不可变:
    int
    ,
    float
    ,
    complex
    ,
    str
    ,
    tuple
    ,
    frozenset
    ,
    bytes
  • 可变:
    list
    ,
    dict
    ,
    set
    ,
    bytearray

对于自定义对象,它们默认是可变的,除非你特意通过一些技巧(比如使用

__slots__
并避免设置可变属性)来使其行为更像不可变对象。理解这些判断方法,能帮助我们更好地预测代码行为,并避免一些常见的陷阱。

热门AI工具

更多
Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3171

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

554

2025.10.31

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

542

2026.02.12

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

6819

2023.08.02

css中float用法
css中float用法

css中float属性允许元素脱离文档流并沿其父元素边缘排列,用于创建并排列、对齐文本图像、浮动菜单边栏和重叠元素。想了解更多float的相关内容,可以阅读本专题下面的文章。

7007

2024.04.28

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

824

2025.10.23

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1818

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2884

2023.09.04

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

40

2026.10.28

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
JavaScript OOP调试技巧视频教程
JavaScript OOP调试技巧视频教程

共5课时 | 1.1万人学习

AJAX入门教程
AJAX入门教程

共6课时 | 2万人学习

WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 21.2万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn