☰
Python 常用数据类型相同点、差异与使用指南
2026/10/8 8:30:27 网站建设 项目流程

前言


Python 的内置类型看起来不多,但真到选型时,很多人还是靠感觉:反正 list 能放东西,dict 也能放东西,那就随便挑一个。结果代码能跑,却在几处细节上悄悄出问题——比如拿 list 当字典的键、拿 tuple 期待它「可变」、把字符串当字符数组去改。


要避免这些,得先抓住几个共同点:它们都是对象,都支持len()、in、迭代,都遵循统一的类型协议。再抓住几个差异点:可变还是不可变、有没有顺序、能不能哈希、允许不允许重复、能不能按下标访问。这几个属性的组合,基本决定了某个容器适合什么、不适合什么。


本文用一张大表把差异摊开,再逐个解释这些属性在实际编码中意味着什么,最后给出选型指南。示例均为 Python 3 写法。顺带说一句,Python 2 已于 2020 年 1 月 1 日停止维护,它独有的unicode、basestring、long类型在 Python 3 里都不存在,统一成了str和int。


一、一张大表横向对比




类型可变有序可哈希允许重复下标访问典型用途



int否不适用是不适用不适用计数、编号

float否不适用是不适用不适用测量值、比例

bool否不适用是不适用不适用标志位

str否是是是是文本

bytes否是是是是二进制数据

bytearray是是否是是可变的二进制缓冲

list是是否是是有序集合、待处理队列

tuple否是通常情况下是是定长记录、字典的键

range否是是否是惰性数字序列

dict是插入序否键唯一按 key按名查找、分组

set是无序否否不适用去重、集合运算

frozenset否无序是否不适用需要可哈希的集合



表里有几个「不适用」值得说明:数字类型没有「顺序」和「下标」的概念,因为它们不是容器;dict 和 set 没有整数下标,只能按 key 或按哈希定位。


二、四个关键属性的含义


可变性(mutability):可变对象创建后能改内容,不可变对象不能。判断标准是「能不能不新建对象就改变值」。lst.append(1)不新建对象,所以 list 可变;s + "a"会新建字符串,所以 str 不可变。不可变带来的好处是安全——对象一旦造好就不会被别的代码偷偷改掉,适合当字典的键、适合当函数默认值。代价是每次「修改」都要造新对象。


有序性(order):这里的「有序」指有没有稳定的位置概念。str、list、tuple 有下标,是真正有序的;set 和 frozenset 无序,遍历顺序不保证;dict 从 Python 3.7 起保证按插入顺序遍历(3.6 里这个行为是 CPython 的实现细节,不要依赖)。注意 dict 的「保序」指的是插入顺序,不是排序——它不会因为键是数字就自动从小到大排。


可哈希性(hashability):一个对象可哈希,意味着它有一个在整个生命周期内不变的哈希值,并且能与其他对象比较相等。可哈希的对象才能当 dict 的键、放进 set。经验规则是:可变容器一律不可哈希。list、dict、set 都不能当键,tuple 和 frozenset 可以——但前提是它们里面的元素也都可哈希。


能否重复:list、tuple、str 允许重复;set、frozenset 不允许;dict 的值可以重复,键不能。


三、这些差异带来的实际后果


第一,能不能当字典的键。下面的例子说明规则怎么用。


# 适用于 Python 3.8+
d = {}
d[(1, 2)] = "点 A" # tuple 可哈希,可以当键
d[frozenset({1, 2})] = "集合" # frozenset 可哈希,可以
print(d[(1, 2)]) # 点 A

# d[[1, 2]] = "x" # TypeError: unhashable type: 'list'
# d[{1, 2}] = "x" # TypeError: unhashable type: 'set'

# 含可变元素的 tuple 也不可哈希
# d[(1, [2, 3])] = "x" # TypeError: unhashable type: 'list'

第二,哈希相等的对象在集合里只算一个。这是很多「去重去不干净」或「数量对不上」问题的来源。


# 适用于 Python 3.8+
s = {1, 1.0, True}
print(len(s)) # 1,三者在哈希和相等判断上视为同一个
print(s == {1}) # True

# 字符串和数字是不同类型,不会互相顶掉
t = {1, "1"}
print(len(t)) # 2

第三,「不可变」和「不可变容器里的内容」是两回事。tuple 本身不可变,但如果它装着 list,那个 list 是可以改的。语言规范的说法是:不可变容器保证的是「直接包含的引用」不变,不保证这些引用指向的对象不变。


# 适用于 Python 3.8+
t = (1, [2, 3])
t[1].append(4) # 合法:改的是里面的 list
print(t) # (1, [2, 3, 4])
# t[1] = [9] # TypeError:不能给 tuple 的元素重新赋值

第四,不同类型之间不能随便比较大小。Python 3 里1 < "a"直接抛TypeError(Python 2 里不同类型的比较是按类型名排序的,这也是 2 和 3 的一大差异)。


四、怎么选:一张决策表




你的需求选它理由



一条固定不变的记录tuple不可变、可解包、可哈希

需要按名字取字段的记录collections.namedtuple 或 dataclass可读性

会不断增删的有序集合list尾部增删均摊 O(1)

只读的有序集合tuple语义明确,防止误改

按 key 找值dict平均 O(1) 查找

去重set插入即去重

需要能当键的集合frozenset不可变所以可哈希

一段数字区间range惰性,不占内存

需要改的字节缓冲bytearray原地修改

二进制数据只读bytes不可变、可哈希



一个实用的心法:先问「要不要改」,再问「要不要按名字查」,最后问「要不要唯一」。三个问题的答案组合起来,容器基本就定了。


常见坑点



  1. 以为「不可变的 tuple」里面的东西也改不了。


❌ 把一条含列表的记录塞进 tuple,以为整条就此冻结。


✅ tuple 只保证元素引用不变;要真正不可变,里面的元素也得是不可变对象,比如把内层 list 换成 tuple。



  1. 把 str 当成可以就地修改的字符数组。


❌s[0] = "H"—— 抛TypeError: 'str' object does not support item assignment。


✅ 字符串不可变,用切片或replace造新串;需要频繁改字符时先转成list再"".join回去。



  1. 用 list 或 set 当字典的键。


❌d[[1, 2]] = "x"抛TypeError: unhashable type: 'list'。


✅ 转成tuple或frozenset再当键。



  1. 忽略了1、1.0、True在集合里是同一个人。


❌ 用集合统计「不同取值有多少种」时,混入了布尔值和数值,数量比预期少。


✅ 统计前统一类型,或改用「类型 + 值」的元组当元素:{(type(x).__name__, x) for x in data}。



  1. 以为 set 会保持某种顺序。


❌ 依赖set的遍历顺序做输出,换个数据就变了。


✅ 输出前显式排序:sorted(s);需要保序去重用dict.fromkeys。



  1. 把 range 当成真的列表用。


❌range(3).append(5)—— range 没有append,它不可变。


✅ range 只用来生成序列,需要可变就list(range(3))。



  1. 不同类型之间比较大小。


❌1 < "a"在 Python 3 里抛TypeError(Python 2 里能比较,属于老代码迁移时的隐藏坑)。


✅ 比较前统一类型,或先判断类型再决定比较方式。



  1. 用==比较浮点数。


❌0.1 + 0.2 == 0.3结果是False,浮点二进制表示无法精确表达这些十进制小数。


✅ 用math.isclose(a, b)做近似比较,或改用decimal.Decimal处理需要精确十进制运算的场景。


总结




属性可变的代表不可变的代表



线性序列list、bytearraystr、bytes、tuple

键值映射dict无内置不可变 dict

集合setfrozenset

数字无int、float、bool(均不可变)



记住三条判据就够了:可变的东西不能当键;有序只保证位置稳定,不代表会排序;不可变容器的不可变性只作用到直接子元素。选型时先定可变性,再定查法,最后定唯一性,比死记硬背每张表都管用。





需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询