跳到主要内容

数据类型

问题

Python 有哪些数据类型?可变与不可变类型有什么区别?深拷贝和浅拷贝的原理是什么?

答案

Python 的数据类型分为基本类型容器类型,核心区别在于可变性

基本数据类型

类型示例可变性说明
int42, 0xff, 0b1010不可变任意精度整数
float3.14, 1e-5不可变双精度浮点数(IEEE 754)
complex3+4j不可变复数
boolTrue, False不可变int 的子类
str"hello", f"{x}"不可变Unicode 字符串
bytesb"hello"不可变字节序列
NoneTypeNone不可变单例空值

容器数据类型

类型示例可变性有序重复
list[1, 2, 3]✅ 可变
tuple(1, 2, 3)❌ 不可变
dict{"a": 1}✅ 可变✅ (3.7+)key 不可重复
set{1, 2, 3}✅ 可变
frozensetfrozenset({1, 2})❌ 不可变
bytearraybytearray(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 始终用 isif 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: listtuple 的区别?什么时候用哪个?

答案

区别listtuple
可变性✅ 可变❌ 不可变
性能较慢(需要额外内存管理)较快(固定大小优化)
内存较大(预留扩容空间)较小
可哈希✅(元素也可哈希时)
语义同质集合(一组同类数据)异质记录(一条记录的各字段)
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: 深拷贝和浅拷贝的区别?什么场景需要用深拷贝?

答案

  • 浅拷贝:只复制对象的第一层引用,嵌套的可变对象仍然共享
  • 深拷贝:递归复制所有层级,完全独立

需要深拷贝的场景:

  1. 嵌套数据结构需要独立修改时
  2. 缓存原始数据不被后续操作污染
  3. 多线程/协程中复制共享数据
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 的字符串为什么是不可变的?有什么好处?

答案

  1. 安全性:字符串常作为 dict key、函数参数、文件路径,不可变保证一致性
  2. 哈希缓存:字符串可以缓存哈希值,dict 查找更快
  3. 线程安全:不可变对象天然线程安全
  4. 内存优化:相同字符串可共享(字符串驻留 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 连续),保持插入顺序。

相关链接