列表和元组,到底用哪一个?

1360 字
7 分钟
列表和元组,到底用哪一个?

如果你写过几天 Python,一定被这个问题绊过:明明都能用 []() 装一堆东西,为什么有时候用 list,有时候又用 tuple?更气人的是,面试还爱问。

其实答案没那么玄学。一句话先给出来:list 是”可变的数据集合”,tuple 是”不可变的数据结构”。下面把这句话拆开讲透。

一、最本质的区别:能不能改#

list 创建之后可以随意增删改:

nums = [1, 2, 3]
nums.append(4) # 加
nums[0] = 99 # 改
nums.remove(2) # 删
print(nums) # [99, 3, 4]

tuple 一旦创建就定型了,任何试图修改元素的操作都会报错:

point = (1, 2)
point[0] = 9 # TypeError: 'tuple' object does not support item assignment

注意,是”元素不能换”,不是”里面一切都不能动”。如果 tuple 里套了一个 list,那个 list 本身还是能改的——但这通常是设计上的坏味道:

mixed = (1, [2, 3])
mixed[1].append(4) # 能跑,但语义上很别扭
print(mixed) # (1, [2, 3, 4])

⚠️ 经验之谈:如果为了让 tuple “看起来可变”而往里塞 list/dict,多半说明你该用 list,甚至该用 dataclass。

二、不可变到底带来了什么好处?#

1. 能当字典的键 / 集合的元素#

这是最实在的区别。字典的键必须是**可哈希(hashable)**的,而可哈希的前提是不可变。

# 用 tuple 做坐标键,没问题
visited = {}
visited[(3, 4)] = True
print((3, 4) in visited) # True
# 用 list 做键,直接报错
bad = {}
bad[[3, 4]] = True # TypeError: unhashable type: 'list'

所以当你需要把”一组值”当成一个整体去查重、当索引时(比如坐标、RGB 颜色、缓存 key),tuple 是唯一选择。

2. 更省内存、创建更快#

tuple 结构比 list 简单,CPython 里分配和访问都更轻。数据量大或者频繁创建时,差异能感知到:

import sys, timeit
print(sys.getsizeof((1, 2, 3))) # 比同长度 list 小
print(sys.getsizeof([1, 2, 3]))
# 创建 1000 万次的耗时对比
t = timeit.timeit("(1,2,3,4,5)", number=10_000_000)
l = timeit.timeit("[1,2,3,4,5]", number=10_000_000)
print(f"tuple: {t:.3f}s list: {l:.3f}s")

日常代码里这点差距基本可忽略,但在热点循环里,tuple 是更稳的选择。

3. 默认更安全#

因为不能改,tuple 在函数间传来传去时,你不用担心它被谁偷偷改掉——这对并发、缓存、配置项都很有意义。

三、真正决定选哪个的:语义#

性能和哈希都是”结果”,最该想清楚的是语义

  • tuple = 异构的、固定结构的数据记录。它装的是”不同的东西”,而且数量固定。像数据库的一行、一个点的坐标、一条 RGB 颜色。
  • list = 同构的、数量可变的数据集合。它装的是”一堆相同的东西”,长度会变化。像购物车、待办事项、一组用户名。

看几个例子就懂了:

# tuple:异构、固定 → 这是"一个点"
point = (10, 20)
# tuple:异构、固定 → 这是"一条记录"
record = ("张三", 28, "北京")
# list:同构、会变 → 这是"一群用户"
users = ["张三", "李四", "王五"]
# list:同构、会变 → 这是"购物车"
cart = ["键盘", "鼠标", "显示器"]

判断口诀很简单:问自己”这个序列的长度会变吗?里面的元素是一类东西吗?”

  • 会变 / 是一类东西 → list
  • 不变 / 是不同字段凑一起 → tuple

四、一张决策表#

场景选哪个原因
待办清单、购物车list数量动态变化
函数返回多个值tuple本质就是返回了个 tuple
字典的键 / 集合元素tuple需要可哈希
坐标、RGB、数据库行tuple异构固定结构
循环里频繁创建的常量序列tuple更省、更快
需要 .append / .sortlist需要可变操作

小知识:Python 函数 return a, b 返回的其实就是一个 tuple,只是语法糖省略了括号。

五、现代写法:别再裸写 tuple 了#

如果 tuple 用来承载”有名字的字段”(比如一条记录),裸 tuple 可读性很差——record[0] 谁知道是名字还是年龄?

优先用更语义化的写法:

from collections import namedtuple
from typing import NamedTuple
from dataclasses import dataclass
# 方式一:namedtuple(轻量、仍是 tuple)
User = namedtuple("User", ["name", "age", "city"])
u = User("张三", 28, "北京")
print(u.name) # 比 u[0] 清楚多了
# 方式二:typing.NamedTuple(带类型注解,推荐)
class User(NamedTuple):
name: str
age: int
city: str
# 方式三:dataclass(要可变、要方法时用)
@dataclass
class User:
name: str
age: int
city: str

规则:不可变 + 简单字段 → NamedTuple;需要修改或带行为 → dataclass

六、两个常见误区#

误区 1:“tuple 比 list 快,所以一律用 tuple。” 错。如果数据要频繁增删改,tuple 反而会逼你不停创建新对象,比 list 还慢。性能优势只在”创建后不再改”时才成立。

误区 2:“tuple 里不能放 list。” 能放,而且解释器不拦你。但一旦这么干,tuple 的”不可变保证”就破了,等于自己给自己挖坑。真要嵌套可变结构,老老实实上 dataclass 或 pydantic。

七、一句话总结#

list 装”一堆会变的同类”,tuple 装”一组固定的异类”。拿不准时,先想语义,再想性能。

如果你愿意,下一篇可以聊聊 dict 和那些容易被忽略的小众容器(defaultdictCounterdeque)——它们才是让代码变优雅的真正利器。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
列表和元组,到底用哪一个?
https://fzy.it.com/posts/列表和元组到底用哪一个/
作者
Fzy
发布于
2026-07-08
许可协议
CC BY-NC-SA 4.0
相关文章 智能推荐
1
用 Scrapy 批量下载网站 PDF:从 14 个索引页到 3176 份文件
技术实践 从多个 HTML 索引页面出发,递归爬取目标域名下的所有 PDF 文件,最终下载 3176 个 PDF(1.8GB)。详解 Scrapy 爬虫设计思路、Content-Type 过滤、非 HTML 扩展名跳过、JOBDIR 持久化等实战技巧。
2
Scrapy 架构深度解析:每个文件在框架中扮演什么角色
技术原理 以 PDF 批量下载项目为实例,逐文件拆解 Scrapy 项目结构(settings.py、spider、middlewares、pipelines、items),解释引擎-调度器-下载器-爬虫-管道五层架构的协作原理。
3
从零构建 Google Trends 爬虫 API:单文件架构的工程实践
技术实践 详解一个基于 Flask + Playwright 的 Google Trends 热搜数据采集服务,涵盖双解析策略降级、指数退避重试、Pending 离线缓存、假成功防御等生产级工程实践。
4
全异步 Yahoo 热门新闻爬虫:模块化架构与三路数据输出实践
技术实践 详解一个基于 Python asyncio + Playwright + Kafka 的 Yahoo Trending 新闻爬虫,涵盖双引擎降级策略、7种选择器链、asyncio 与同步 requests 混用方案,以及本地 JSON、Kafka、ODS 三路并行数据输出设计。
5
从单机到集群:分布式爬虫后端架构设计指南
系统设计 设计一套完整的分布式爬虫系统——任务队列派发、VPS 集群执行、Kafka 数据总线分发、多数据库(MySQL/MongoDB/ES/Doris/Redis)各司其职,包含故障处理和容错设计。
随机文章 随机推荐
Fzy
Full-Stack Developer · Tech Enthusiast · Lifelong Learner
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
站点统计
文章
19
分类
10
标签
50
总字数
35,030
运行时长
0
最后活动
0 天前

目录