数据类型
问题
Python 有哪些数据类型?可变与不可变类型有什么区别?深拷贝和浅拷贝的原理是什么?
答案
Python 的数据类型分为基本类型和容器类型,核心区别在于可变性。
基本数据类型
| 类型 | 示例 | 可变性 | 说明 |
|---|---|---|---|
int | 42, 0xff, 0b1010 | 不可变 | 任意精度整数 |
float | 3.14, 1e-5 | 不可变 | 双精度浮点数(IEEE 754) |
complex | 3+4j | 不可变 | 复数 |
bool | True, False | 不可变 | int 的子类 |
str | "hello", f"{x}" | 不可变 | Unicode 字符串 |
bytes | b"hello" | 不可变 | 字节序列 |
NoneType | None | 不可变 | 单例空值 |
容器数据类型
| 类型 | 示例 | 可变性 | 有序 | 重复 |
|---|---|---|---|---|
list | [1, 2, 3] | ✅ 可变 | ✅ | ✅ |
tuple | (1, 2, 3) | ❌ 不可变 | ✅ | ✅ |
dict | {"a": 1} | ✅ 可变 | ✅ (3.7+) | key 不可重复 |
set | {1, 2, 3} | ✅ 可变 | ❌ | ❌ |
frozenset | frozenset({1, 2}) | ❌ 不可变 | ❌ | ❌ |
bytearray | bytearray(b"hi") | ✅ 可变 | ✅ | ✅ |
可变与不可变
# 不可变类型:修改值会创建新对象
a = "hello"
print(id(a)) # 140234866584880
a += " world"
print(id(a)) # 140234866585008 ← id 变了,是新对象
# 可变类型:原地修改,对象不变
b = [1, 2, 3]
print(id(b)) # 140234866123456
b.append(4)
print(id(b)) # 140234866123456 ← id 不变,同一对象
不可变容器中的可变元素
tuple 不可变指的是元组内的引用不可变,如果引用指向可变对象,该对象本身可以修改:
t = ([1, 2], [3, 4])
t[0].append(3) # ✅ t = ([1, 2, 3], [3, 4])
t[0] = [5, 6] # ❌ TypeError: 不能替换引用
小整数池与字符串驻留
# CPython 缓存 [-5, 256] 的整数
a = 256
b = 256
print(a is b) # True —— 同一对象
a = 257
b = 257
print(a is b) # False —— 不同对象(交互模式下)
# 字符串驻留(intern):短字符串和标识符会被缓存
a = "hello"
b = "hello"
print(a is b) # True
== vs is
==比较值是否相等(调用__eq__)is比较身份是否相同(id()是否相等)- 判断
None始终用is:if x is None
深拷贝与浅拷贝
import copy
original = [[1, 2], [3, 4], {"key": "value"}]
# 浅拷贝:只复制第一层,内层仍然共享引用
shallow = copy.copy(original)
# 等价方式:list(original)、original[:]、original.copy()
shallow[0].append(3)
print(original[0]) # [1, 2, 3] ← 原数据也被修改了!
# 深拷贝:递归复制所有层级
deep = copy.deepcopy(original)
deep[1].append(5)
print(original[1]) # [3, 4] ← 原数据不受影响
类型转换
# 显式转换
int("42") # 42
float("3.14") # 3.14
str(42) # "42"
list("abc") # ['a', 'b', 'c']
tuple([1, 2, 3]) # (1, 2, 3)
set([1, 2, 2, 3]) # {1, 2, 3}
dict([("a", 1)]) # {"a": 1}
# 布尔转换 — 假值(Falsy)
bool(0) # False
bool(0.0) # False
bool("") # False
bool([]) # False
bool({}) # False
bool(None) # False
bool(set()) # False
# 其余均为 True
类型判断
# type() — 精确匹配,不考虑继承
type(42) == int # True
type(True) == int # True(bool 是 int 子类)
# isinstance() — 推荐方式,支持继承链
isinstance(42, int) # True
isinstance(True, int) # True
isinstance(True, bool) # True
# 多类型判断
isinstance(42, (int, float)) # True
常见面试问题
Q1: Python 中可变和不可变类型的区别?为什么 dict 的 key 必须是不可变类型?
答案:
可变类型(list, dict, set)的值可以原地修改而不改变对象 id;不可变类型(int, str, tuple)修改值会创建新对象。
dict 的 key 必须是可哈希的(hashable),即实现了 __hash__ 方法且哈希值在生命周期内不变。可变类型的值可以改变,导致哈希值改变,破坏字典的查找机制,因此不能作为 key。
# 不可变类型可哈希
hash(42) # ✅
hash("hello") # ✅
hash((1, 2, 3)) # ✅
# 可变类型不可哈希
hash([1, 2, 3]) # ❌ TypeError: unhashable type: 'list'
hash({"a": 1}) # ❌ TypeError: unhashable type: 'dict'
Q2: list 和 tuple 的区别?什么时候用哪个?
答案:
| 区别 | list | tuple |
|---|---|---|
| 可变性 | ✅ 可变 | ❌ 不可变 |
| 性能 | 较慢(需要额外内存管理) | 较快(固定大小优化) |
| 内存 | 较大(预留扩容空间) | 较小 |
| 可哈希 | ❌ | ✅(元素也可哈希时) |
| 语义 | 同质集合(一组同类数据) | 异质记录(一条记录的各字段) |
import sys
# 内存对比
print(sys.getsizeof([1, 2, 3])) # 88 bytes
print(sys.getsizeof((1, 2, 3))) # 64 bytes
# tuple 可作为 dict key
locations = {(35.6, 139.7): "Tokyo"}
选择原则:需要修改用 list,不需要修改用 tuple。函数返回多值、字典 key、作为集合元素时用 tuple。
Q3: 深拷贝和浅拷贝的区别?什么场景需要用深拷贝?
答案:
- 浅拷贝:只复制对象的第一层引用,嵌套的可变对象仍然共享
- 深拷贝:递归复制所有层级,完全独立
需要深拷贝的场景:
- 嵌套数据结构需要独立修改时
- 缓存原始数据不被后续操作污染
- 多线程/协程中复制共享数据
import copy
# 循环引用处理
a = [1, 2]
a.append(a) # a 引用自身
b = copy.deepcopy(a) # ✅ deepcopy 能正确处理循环引用
Q4: is 与 == 的区别?
答案:
is比较两个对象是否是同一个对象(比较id())==比较两个对象的值是否相等(调用__eq__)
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True — 值相等
print(a is b) # False — 不是同一对象
# None 比较必须用 is
x = None
if x is None: # ✅ 推荐
pass
if x == None: # ❌ 不推荐(可能被重写的 __eq__ 干扰)
pass
Q5: Python 中如何判断变量类型?type() 和 isinstance() 的区别?
答案:
type()返回精确类型,不考虑继承isinstance()考虑继承链,是推荐方式
class Animal: pass
class Dog(Animal): pass
d = Dog()
type(d) == Animal # False
isinstance(d, Animal) # True ← 推荐
Q6: Python 的字符串为什么是不可变的?有什么好处?
答案:
- 安全性:字符串常作为 dict key、函数参数、文件路径,不可变保证一致性
- 哈希缓存:字符串可以缓存哈希值,dict 查找更快
- 线程安全:不可变对象天然线程安全
- 内存优化:相同字符串可共享(字符串驻留 interning)
# 字符串驻留示例
import sys
a = sys.intern("hello_world")
b = sys.intern("hello_world")
print(a is b) # True — 强制驻留到同一对象
Q7: dict 在 Python 3.7+ 保证有序,底层是如何实现的?
答案:
Python 3.7+ 的 dict 使用紧凑字典实现:
# 逻辑结构
indices = [None, 1, None, 0, None, 2, None, None] # 哈希表(稀疏)
entries = [ # 条目数组(紧凑,按插入顺序)
(hash_a, "a", 1), # index 0
(hash_b, "b", 2), # index 1
(hash_c, "c", 3), # index 2
]
- indices:稀疏哈希表,值是 entries 数组的索引
- entries:紧凑数组,按插入顺序存储键值对
好处:内存减少 20-25%,遍历更快(entries 连续),保持插入顺序。