文章目录

    • 一、故事从变量赋值说起
    • 二、不可变类型 (Immutable Types)
    • 三、可变类型 (Mutable Types)
    • 四、一个常见的陷阱:当元组遇到列表
    • 五、为什么这个区别如此重要?
      • 1. 函数参数的传递
      • 2. 字典的键 (Dictionary Keys)
      • 3. 函数的默认参数陷阱
    • 六、进阶话题与扩展
      • 1. 浅拷贝 vs. 深拷贝:`copy` 与 `deepcopy`
      • 2. `+=` 运算符:可变与不可变对象的差异
      • 3. CPython 的对象缓存机制
      • 4. 并发中的可变对象
      • 5. 冻结(只读)数据结构
      • 6. 性能小贴士
    • 总结

在 Python 的学习和实践中,有一个核心概念是绕不开的,那就是"可变"(Mutable)与"不可变"(Immutable)类型。刚开始,你可能觉得这只是个定义问题,但随着你写出更复杂的程序,你会发现,能否深刻理解这两者的区别,直接决定了你的代码是否健壮、高效,以及是否会踩到一些意想不到的"坑"。

这篇文章将带你由浅入深,彻底搞懂这个关键概念。

一、故事从变量赋值说起

在 Python 中,我们常说"变量是贴在对象上的标签"。理解这句话是后续一切的基础。

当你写下 x = 100 时,Python 做了两件事:

  1. 在内存中创建了一个代表数字 100 的对象。
  2. 创建了一个名为 x 的变量(标签),然后把它"贴"到 100 这个对象上。

那么,当我们"修改"变量时,会发生什么呢?这就要看对象的类型了。

二、不可变类型 (Immutable Types)

顾名思义,不可变类型的对象,其值在创建后就不能被改变。任何对它的"修改"操作,实际上都会创建一个全新的对象。

常见的不可变类型包括:

  • 数字: int, float, bool
  • 字符串: str
  • 元组: tuple
  • 冻结集合: frozenset

让我们用代码和内存地址 id() 来眼见为实。

示例 1: 字符串 str

my_string = "hello"
print(f"初始字符串: '{my_string}', 内存地址: {id(my_string)}")# 尝试"修改"字符串
my_string = my_string + " world"print(f"修改后字符串: '{my_string}', 内存地址: {id(my_string)}")

输出:

初始字符串: 'hello', 内存地址: 4389754112
修改后字符串: 'hello world', 内存地址: 4389754224

看到了吗?内存地址变了!Python 并没有修改原来的 "hello" 对象,而是创建了一个全新的 "hello world" 对象,然后把 my_string 这个标签从旧对象身上撕下来,贴到了新对象上。

三、可变类型 (Mutable Types)

与不可变类型相反,可变类型的对象,其值可以在创建后被原地修改,而不需要创建新对象。

常见的可变类型包括:

  • 列表: list
  • 字典: dict
  • 集合: set
  • 字节数组: bytearray

示例 2: 列表 list

my_list = [1, 2, 3]
print(f"初始列表: {my_list}, 内存地址: {id(my_list)}")# 尝试修改列表
my_list.append(4)print(f"修改后列表: {my_list}, 内存地址: {id(my_list)}")

输出:

初始列表: [1, 2, 3], 内存地址: 4510696320
修改后列表: [1, 2, 3, 4], 内存地址: 4510696320

内存地址完全没变!append 操作是在原始列表对象上直接进行的修改。my_list 这个标签自始至终都贴在同一个对象上。

四、一个常见的陷阱:当元组遇到列表

元组 tuple 是不可变的,对吧?这意味着我们不能增加或删除它的元素。但如果元组里包含了可变类型的对象(比如列表),情况就变得有趣了。

# 元组本身是不可变的
my_tuple = (1, 2, ['a', 'b'])print(f"初始元组: {my_tuple}, 内存地址: {id(my_tuple)}")# 尝试修改元组中的列表
my_tuple[2].append('c')print(f"修改后元组: {my_tuple}, 内存地址: {id(my_tuple)}")# 尝试直接修改元组元素(这会报错)
# my_tuple[0] = 99 # TypeError: 'tuple' object does not support item assignment

输出:

初始元组: (1, 2, ['a', 'b']), 内存地址: 4474840192
修改后元组: (1, 2, ['a', 'b', 'c']), 内存地址: 4474840192

元组的内存地址没变,但它里面的列表内容却实实在在地改变了。

结论:不可变性指的是对象本身的结构固定。对于元组来说,是它所包含的元素的"引用"不可变。它引用的那个列表还是那个列表(内存地址没变),但列表自身的内容是可以被修改的。

五、为什么这个区别如此重要?

理解可变与不可变,在实际编程中至关重要,尤其体现在以下几个方面:

1. 函数参数的传递

在 Python 中,函数参数传递的是对象的引用。

  • 如果传递的是不可变对象,你在函数内部无法修改原始调用者的变量。
  • 如果传递的是可变对象,你在函数内部的修改会直接影响到原始对象。
def process_data(immutable_str, mutable_list):immutable_str = "changed"mutable_list.append(99)print(f"函数内部: str='{immutable_str}', list={mutable_list}")s = "original"
l = [1, 2]process_data(s, l)print(f"函数外部: str='{s}', list={l}")

输出:

函数内部: str='changed', list=[1, 2, 99]
函数外部: str='original', list=[1, 2, 99]

看到结果了吗?字符串 s 没变,但列表 l 被永久地改变了。

2. 字典的键 (Dictionary Keys)

字典的键必须是不可变类型

这是因为字典的查找效率极高,其内部依赖于对键进行哈希运算(hash())。哈希值要求在对象的生命周期内保持不变。

  • 不可变对象的值固定,哈希值也固定。
  • 可变对象的值可以变,如果允许它当键,它的哈希值也可能变,整个字典的结构就会崩溃。
my_dict = {}
my_dict["key"] = "value"  # 字符串可以当键
my_dict[123] = "value"    # 整数可以当键
my_dict[(1, 2)] = "value" # 元组可以当键# 尝试用列表当键
try:my_dict[[1, 2]] = "value"
except TypeError as e:print(e) # 输出: unhashable type: 'list'

3. 函数的默认参数陷阱

这是一个经典的面试题,也是新手最容易犯的错误:永远不要使用可变类型作为函数的默认参数

def add_item(item, item_list=[]):item_list.append(item)return item_list# 第一次调用
print(add_item(1)) # 输出: [1]# 第二次调用
print(add_item(2)) # 输出: [1, 2] (你可能期望的是 [2])# 第三次调用
print(add_item(3)) # 输出: [1, 2, 3]

原因:函数的默认参数 item_list=[] 只在函数定义时被创建一次。后续所有不提供 item_list 参数的调用,都共享着同一个列表对象。

正确做法:

def add_item_fixed(item, item_list=None):if item_list is None:item_list = [] # 在函数体内创建新列表item_list.append(item)return item_list

六、进阶话题与扩展

1. 浅拷贝 vs. 深拷贝:copydeepcopy

Python 标准库 copy 模块提供两种复制策略:

  • 浅拷贝 (copy.copy):仅复制最外层容器,新容器内部仍引用原有子对象。
  • 深拷贝 (copy.deepcopy):递归复制整棵对象图,确保任何层级的修改互不影响。
import copya = [1, [2, 3]]
b = copy.copy(a)      # 浅拷贝
c = copy.deepcopy(a)  # 深拷贝a[1].append(4)
print(b)  # [1, [2, 3, 4]]  —— 受影响
print(c)  # [1, [2, 3]]     —— 不受影响

2. += 运算符:可变与不可变对象的差异

对于不可变对象(如 str, tuple),x += y 会创建 新对象;而对 list 等可变对象,+= 会就地修改。

s = "abc"
print(id(s))s += "d"
print(id(s))  # 地址变化,说明创建了新对象lst = [1, 2]
print(id(lst))lst += [3]
print(id(lst))  # 地址不变,说明原地修改

3. CPython 的对象缓存机制

为了性能,CPython 会缓存 小整数 (-5~256)部分短字符串。因此下面代码在 CPython 中可能打印 True,并不代表语言层面对这些对象做了特殊对待,而是实现细节:

a = 100
b = 100
print(a is b)  # True (CPython)

其它解释器(PyPy、Jython 等)不一定有相同表现,因此不要依赖该特性来做逻辑判断。

4. 并发中的可变对象

在多线程 / 协程场景下,共享可变对象必须采用同步原语,否则会产生竞态条件或数据损坏;不可变对象天然只读,可安全共享。

from threading import Lockcounter = 0
lock = Lock()def inc():global counterwith lock:counter += 1

5. 冻结(只读)数据结构

  • frozenset:不可变集合,可作为字典键;
  • types.MappingProxyType:为字典提供只读视图;
  • 三方库 immutables.Map:高性能、结构共享的持久化不可变映射。

6. 性能小贴士

  • 频繁拼接字符串时,先收集到 list 再使用 ''.join(chunks),可避免创建大量中间对象;
  • 对可变对象使用就地修改操作(如 list.append, 切片赋值)通常更节省内存、提升性能。

总结

特性不可变类型 (Immutable)可变类型 (Mutable)
定义创建后值不能被改变创建后值可以被原地修改
示例int, str, tuple, frozensetlist, dict, set
修改行为创建新对象,变量指向新对象在原对象上修改,变量指向不变
字典键可以作为字典的键不可以作为字典的键
函数传参函数内修改不影响外部原变量函数内修改会影响外部原变量

掌握 Python 的可变与不可变类型,是写出清晰、可预测且无 Bug 代码的基石。希望这篇文章能让你对这个概念有更深入的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88366.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88366.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/88366.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wpf使用webview2显示网页内容(最低兼容.net framework4.5.2)

wpf使用webview2显示网页内容(最低兼容.net framework4.5.2 一、核心功能与架构混合开发支持‌进程隔离模型‌通信机制‌二、核心优势性能与兼容性‌跨平台部署‌开发效率‌安全机制‌三、适用场景四、开发部署要点WebView2 是微软推出的现代浏览器控件,基于 Chromium 内核的 …

MySQL断开连接后无法正常启动解决记录

问题现象 夜里23点MySQL在还原备份的时候断开连接,尝试重启,表面上是运行中实际上无法通过命令端连接,无法正常启动。 问题检查 可以使用 systemctl start mysql 但是没有监听 3306端口 mysql -ucosmic -p 提示无法找到socket文件 删除原先的…

隧道安全监测系统的应用意义

随着我国交通基础设施建设的快速发展,公路、铁路及城市地铁隧道数量不断增加,隧道安全问题日益凸显。隧道作为地下封闭空间,受地质条件、施工质量、运营环境等多因素影响,易出现结构变形、渗漏水、衬砌开裂等安全隐患。一旦发生事…

前端UI逻辑复杂可以用什么设计模式

中介者模式 当UI组件间存在复杂交互或多个组件需共享状态时,中介者模式能集中管理事件分发和状态更新,减少组件间的直接依赖,提升解耦性。 vue实现中介者模式 在Vue中实现中介者模式,你可以通过创建一个全局的事件中心&#xff08…

WIFI协议全解析05:WiFi的安全机制:IoT设备如何实现安全连接?

🔐 WiFi的安全机制:IoT设备如何实现安全连接?“我的设备明明连上WiFi了,为什么还是能被‘蹭网’?” “WPA3 是什么?ESP32 支持吗?” “我做了MQTT加密就算安全了吗?”IoT设备连接WiF…

HTTP 请求体类型详解:选择最适合的数据提交格式

HTTP 请求体类型详解:选择最适合的数据提交格式 🚀 本文全面解析 HTTP 请求中不同 Content-Type 的适用场景、数据结构与优劣势,帮助开发者高效选择数据传输方案。 📌 目录 核心请求体类型对比详细类型解析最佳实践指南总结 &am…

C语言 | 函数核心机制深度解构:从底层架构到工程化实践

个人主页-爱因斯晨 文章专栏-C语言 引言 最近偷懒了,迷上了三国和李贺。给大家分享一下最喜欢的一句诗:吾不识青天高黄地厚,唯见月寒日暖来煎人寿。我还不是很理解27岁的李贺,如何写出如此绝笔。 正文开始,今天我们…

uniapp真机调试“没有检测到设备,请插入设备或启动模拟器后点击刷新再试”

当真机调试,运行到安卓 APP基座 时,有时会检测不到设备,显示下面的问题:此时,可以通过下面的几种方法进行排查:1.在手机中找到“开发者选项”选项(可在设置中搜索,如搜索不到&#x…

使用langchain连接llama.cpp部署的本地deepseek大模型开发简单的LLM应用

langchain是一个基于python实现的开源LLM开发框架,llama.cpp是一个基于C框架可以在本地部署大模型并开放服务端接口开放给外部应用使用。 本文结合langchain和llama.cpp,在本地部署轻量级的deepseek大模型,并构建一个简单的链式LLM应用&…

Serverless 数据库来了?无服务器数据库 vs 传统数据库有何不同?

随着云计算技术的迅猛发展,无服务器(Serverless)架构逐渐成为一种主流趋势。其中,Serverless 数据库作为云原生应用的重要组成部分,为开发者提供了前所未有的灵活性和成本效益。相比传统的数据库管理方式,S…

【读书笔记】如何画好架构图:架构思维的三大底层逻辑

【读书笔记】如何画好架构图:架构思维的三大底层逻辑 架构图并非技术人的“画功比拼”,而是一个团队、一个系统、一次项目从混沌走向清晰的关键抓手。它是系统的视觉语言,是让技术人员、产品经理、运营甚至老板都能站在统一上下文下讨论的“…

Maven 编译过程中发生了 Java Heap Space 内存溢出(OutOfMemoryError)

这个是我最近遇到的,因为本人最近换了电脑,这个电脑的前任是配置好了环境,但是当我用这个环境去做另外一个项目的时候,在maven构建war和jar包的时候,报了这个内存溢出mvn clean install 就给我报错了[ERROR] Failed to…

C++ 模板参数展开

C 模板参数展开一、获取可变参数大小二、通过模版循环继承的方式来展开可变参数三、改用Using去实现循环继承一、获取可变参数大小 背景&#xff1a; FLen<int, char, long> Len; 我想要获取模板参数类型的总大小 template<typename T,typename ...ParamTypes> c…

零基础入门物联网-远程门禁开关:云平台创建

一、 onenet云平台注册创建 远程开关的信息传输依赖云平台&#xff0c;本教程以 OneNET - 中国移动物联网开放平台为例进行操作&#xff0c;具体步骤如下&#xff1a; 1、平台账号创建 点击 OneNET - 中国移动物联网开放平台进入官网 点击页面中的 “登录” 按钮&#xff0c;…

html页面,当鼠标移开A字标就隐藏颜色框

html页面代码&#xff1a;<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>颜色选择器</title><style>body {font-family: "Microsoft YaHei", sans-serif;padding: 20px;}.c…

保姆级搭建harbor私有仓库与docker-ce教程与使用教程

搭建harbor仓库[rootharbor ~]# vim cat /etc/host192.168.121.12 harbor[rootharbor ~]# vim /etc/hostnameharbor导入 harbor 项目镜像[rootharbor ~]# tar -zxf harbor-v2.9.2.tgz -C /usr/local/[rootharbor ~]# cd /usr/local/harbor[rootharbor harbor]# docker load -i…

【Linux】Rocky Linux 安装 Docker 与 Docker-Compose

Docker 安装步骤 1. 安装必要的软件包 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo2. 安装Docker sudo yum install docker-ce docker-ce-cli containerd.io如果出现 SSL 证书错误&#xf…

揭示独特模式:Elasticsearch 中 significant terms 聚合指南

作者&#xff1a;来自 Elastic Alexander Dvila 了解如何使用 significant terms 聚合来发现你数据中的洞察。 更多阅读&#xff1a;Elasticsearch&#xff1a;significant terms aggregation Elasticsearch 拥有大量新功能&#xff0c;可以帮助你为你的使用场景构建最佳搜索解…

pandas.DataFrame中axis参数

明确axis0与axis1的区别和联系&#xff0c; 假设有一个 DataFrame&#xff1a;indexAB012134axis0&#xff08;沿行方向&#xff09;&#xff1a; 操作会垂直向下进行&#xff0c;对每一列单独处理。 例如&#xff1a;df.sum(axis0) 会对列 A 和列 B 分别求和&#xff0c;结果是…

深度学习 最简单的神经网络 线性回归网络

用最简单的线性模型讲清 神经网络 训练全流程,让你 5 分钟看懂AI 是怎么学会预测的 🔥 1 真实神经元结构 📊 真实神经元包括: 树突 接收其他神经元传来的电信号(输入)。 细胞核 负责整合输入信号并产生动作电位。 轴突 传导动作电位到下一个神经元。 突触 释放神经递质…