Python 中不同的数据类型,虽然都可以保存数据,但它们管理数据的方式并不一样。
真正需要理解的是:
这个数据结构靠什么找到和管理里面的数据?
1. List:按照位置管理元素
python
lst = [10, 20, 30]
可以理解为:
text
位置 0 → 10
位置 1 → 20
位置 2 → 30
所以可以:
python
lst[1]
得到:
text
20
也可以:
python
lst[1] = 100
变成:
python
[10, 100, 30]
因此:
list 按位置管理元素,每个位置都可以重新指向其他对象。
2. Tuple:位置存在,但位置指向的对象不能更换
python
t = (10, 20, 30)
同样可以理解为:
text
位置 0 → 10
位置 1 → 20
位置 2 → 30
所以可以:
python
t[1]
但不能:
python
t[1] = 100
因为 tuple 创建以后:
每一个位置所指向的对象不能再被换掉。
但是:
python
t = (10, [20, 30])
是允许的。
此时:
text
位置 0 → 10
位置 1 → [20, 30]
可以:
python
t[1].append(40)
得到:
python
(10, [20, 30, 40])
因为 t[1] 仍然指向原来的 list,只是 list 自己内部的数据发生了变化。
因此:
tuple 的不可变,是位置所指向的对象不能更换,并不代表被指向的对象本身一定不能变化。
3. Set:按照 Hash 管理成员
python
s = {10, 20, 30}
set 没有:
python
s[0]
因为 set 不是按照位置来管理元素。
元素加入 set 时,Python 会:
text
元素
→ 计算 hash
→ 根据 hash 计算内部存储位置
→ 存入 set
例如:
text
10 → hash(10) → 计算位置 → 存放 10
20 → hash(20) → 计算位置 → 存放 20
30 → hash(30) → 计算位置 → 存放 30
查询:
python
20 in s
大致过程:
text
20
→ 计算 hash(20)
→ 根据 hash 找到可能的位置
→ 检查是不是 20
→ 返回 True / False
所以:
set 时按照 hash 管理成员,而不是按下标管理元素。
这也是为什么 set 中的元素必须可以被 hash()。
例如:
python
hash(10) # 可以
hash("hello") # 可以
所以它们可以放入 set。
而:
python
hash([1, 2])
会报错:
text
TypeError: unhashable type: 'list'
因此 list 不能直接作为 set 的元素。
4. Dict:Key 按照 Hash 管理,Value 跟着 Key 保存
python
d = {
"name": "Tom",
"age": 20
}
dict 不是按照:
text
第 0 个
第 1 个
来查找数据。
它主要根据 key 的 hash 管理数据。
例如:
python
d["name"]
Python 大致会:
text
"name"
→ 计算 hash("name")
→ 根据 hash 找到内部位置
→ 找到 key "name"
→ 得到对应的 value "Tom"
所以字典内部管理的是:
text
key + value
其中:
key 负责定位,value 跟着 key 保存。
因此字典的 key 必须可哈希:
python
{
"name": "Tom", # str 可以
10: "hello" # int 可以
}
但不能:
python
{
[1, 2]: "hello"
}
因为 list 不可哈希。
而 value 没有这个限制:
python
d = {
"data": [1, 2, 3]
}
完全可以。
因为真正用来定位的是:
text
"data"
而不是后面的 list。
5. String:按照位置读取字符,但字符串本身不能修改
python
s = "Python-DuDu"
可以理解为:
text
位置 0 → P
位置 1 → y
位置 2 → t
位置 3 → h
位置 4 → o
位置 5 → n
...
所以可以:
python
s[0]
得到:
text
P
但是不能:
python
s[0] = "J"
因为字符串是不可变的。
如果写:
python
s = "DuDu"
并不是把原来的 "Python" 修改成 "DuDu"。
而是让变量 s 改去指向新的字符串对象。
因此:
str 按照位置读取,但字符串创建以后内容不能修改。
字符串也是可哈希的:
python
hash("Python")
所以字符串可以作为:
text
set 元素
dict 的 key
6. 为什么 List、Dict、Set 本身不能 Hash?
它们都有一个共同特点:
自身内容可以直接发生变化。
例如:
python
lst.append(10)
python
d["age"] = 30
python
s.add(40)
如果这种对象作为哈希表中的元素或 key,而它们的内容变化又导致 hash 发生变化,就可能导致:
text
存进去时使用一个 hash
后来查找时变成另一个 hash
这样原来的位置就可能找不到。
所以 Python 将常见的:
text
list
dict
set
设计成不可哈希。
7. 常见类型总结
| 类型 | 主要管理方式 | 是否可修改 | 是否可 Hash |
|---|---|---|---|
list |
按位置管理元素 | 是 | 否 |
tuple |
按位置管理,位置所指向的对象不能更换 | 否 | 内部所有元素都可 Hash 时可以 |
set |
根据元素的 Hash 计算内部存储和查找位置 | 是 | set 本身不可以 |
dict |
根据 Key 的 Hash 计算内部存储和查找位置 | 是 | dict 本身不可以 |
str |
按位置读取字符 | 否 | 可以 |
int |
单个不可变值 | 否 | 可以 |
frozenset |
根据元素的 Hash 计算内部存储和查找位置 | 否 | 可以 |
8. 总结
-
List:按位置存取,每个位置都可以重新指向其他对象。
-
Tuple :也按位置存取,但每个位置所指向的对象不能被替换。
不过,被指向的对象本身可以是可变对象;如果对象本身可变,其内部数据仍然可以发生改变。
-
String:按位置读取,但自身内容不能修改。
-
Set :根据元素的
Hash进行存储和查找。 -
Dict :根据
Key的Hash进行存储和查找。 -
Set 的元素 和 Dict 的 Key 必须可哈希。
在 Python 常见内置类型中,可以简单理解为:
- 不可变类型通常可哈希
- 可变类型通常不可哈希
需要注意:
Tuple虽然属于 Python 的不可变数据类型,但如果其中包含不可哈希的对象,例如List,那么这个Tuple整体也不可哈希。