作者:没有四次元口袋的蓝胖
日期:2026-09-19
标签:Python, 数据结构, 列表, 字典
Python数据结构详解
Python内置了四种核心数据结构:列表(list)、元组(tuple)、字典(dict)、集合(set)。作为Java开发者,你可以把它们分别对应到 ArrayList、不可变数组/Tuple、HashMap、HashSet。但这四者在Python中的用法比Java更灵活,特别是列表推导式、切片操作这些语法糖,写好了代码能少一半。这篇笔记逐个拆解,重点讲清常用操作、Java对比和面试坑点。
核心掌握:列表的增删改查与切片、列表推导式、元组解包、字典遍历与推导式、集合运算。
一、列表 list
1.1 基本介绍
列表是Python中最常用的数据结构,有序、可变、可重复 ,对应Java的 ArrayList。但比ArrayList灵活得多------一个列表可以装不同类型的元素,也不需要指定泛型。
python
# 创建列表
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True] # 可以混合类型,Java ArrayList<Object>
empty = [] # 空列表
from_range = list(range(5)) # [0, 1, 2, 3, 4]
# 注意:list() 是内置函数,不要用作变量名!
java
// Java 对比
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Object> mixed = new ArrayList<>(); // 需要手动add,且丢失类型安全
List<Integer> empty = new ArrayList<>();
1.2 增(append/extend/insert)
python
fruits = ["apple", "banana"]
# append:末尾添加一个元素
fruits.append("cherry") # ["apple", "banana", "cherry"]
# extend:末尾批量添加(接受任何可迭代对象)
fruits.extend(["date", "elderberry"]) # ["apple", "banana", "cherry", "date", "elderberry"]
fruits.extend("fig") # 字符串被拆成字符!['...','f','i','g']
# insert:指定位置插入(索引从0开始)
fruits.insert(0, "avocado") # ["avocado", "apple", "banana", "cherry", ...]
fruits.insert(2, "blueberry") # 在索引2处插入
java
// Java 对比
fruits.add("cherry"); // = append
fruits.addAll(List.of("date", "elderberry")); // = extend
fruits.add(0, "avocado"); // = insert
1.3 删(remove/pop/del)
python
nums = [1, 2, 3, 4, 5, 3]
# remove:按值删除(只删第一个匹配)
nums.remove(3) # [1, 2, 4, 5, 3],只删了第一个3
# pop:按索引删除并返回值(默认删最后一个)
last = nums.pop() # 返回3,nums变成[1, 2, 4, 5]
first = nums.pop(0) # 返回1,nums变成[2, 4, 5]
# del:按索引删除(不返回值),也可以删除切片
del nums[0] # 删除第一个元素
del nums[1:3] # 删除索引1到2的元素(左闭右开)
# clear:清空列表
nums.clear() # []
java
// Java 对比
nums.remove(Integer.valueOf(3)); // 按值删,注意要用包装类!
int last = nums.remove(nums.size() - 1); // 按索引删
nums.clear(); // 清空
坑点 :Java的 remove(int) 按索引删,remove(Object) 按值删。Python分得更清楚:remove(值) 按值删,pop(索引) 按索引删。
1.4 改(索引赋值)
python
nums = [1, 2, 3, 4, 5]
# 单元素修改
nums[0] = 10 # [10, 2, 3, 4, 5]
# 切片赋值(可以替换多个元素,新旧长度可以不同!)
nums[1:3] = [20, 30, 40] # [10, 20, 30, 40, 4, 5],用3个元素替换了2个
nums[1:3] = [] # [10, 40, 4, 5],等于删除
1.5 查(索引/切片)
python
nums = [10, 20, 30, 40, 50, 60, 70]
# 索引访问(和Java一样从0开始)
print(nums[0]) # 10
print(nums[-1]) # 70,负数索引:-1是最后一个,-2是倒数第二个
# 切片 list[start:end:step](左闭右开!)
print(nums[1:4]) # [20, 30, 40],索引1到3
print(nums[:3]) # [10, 20, 30],从开头到索引2
print(nums[3:]) # [40, 50, 60, 70],从索引3到末尾
print(nums[::2]) # [10, 30, 50, 70],步长2
print(nums[::-1]) # [70, 60, 50, 40, 30, 20, 10],反转列表
print(nums[4:1:-1]) # [50, 40, 30],从索引4到索引2(反向)
# 注意:切片不会越界!
print(nums[0:100]) # [10, 20, 30, 40, 50, 60, 70],自动截断
1.6 切片操作详解
切片是Python最强大的语法之一,格式为 list[start:end:step]:
| 表达式 | 含义 | 结果 |
|---|---|---|
nums[1:4] |
索引1到3 | [20, 30, 40] |
nums[:3] |
开头到索引2 | [10, 20, 30] |
nums[3:] |
索引3到末尾 | [40, 50, 60, 70] |
nums[:] |
全部(浅拷贝) | 整个列表的副本 |
nums[::2] |
每隔一个取 | [10, 30, 50, 70] |
nums[::-1] |
反转 | [70, 60, 50, 40, 30, 20, 10] |
nums[-3:] |
最后3个 | [50, 60, 70] |
1.7 列表推导式
列表推导式是Python的"杀手级"语法,用一行代码完成创建+过滤+转换,等价于Java Stream 的操作。
python
# 基本形式:[表达式 for 变量 in 可迭代对象]
squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 带条件过滤:[表达式 for 变量 in 可迭代对象 if 条件]
evens = [x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]
# 带转换+过滤
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
# [0, 4, 16, 36, 64]
# 嵌套循环
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
# [(0,1), (0,2), (1,0), (1,2), (2,0), (2,1)]
# 字符串处理
words = ["hello", "world", "python"]
upper = [w.upper() for w in words]
# ["HELLO", "WORLD", "PYTHON"]
java
// Java Stream 对比
// Python: [x ** 2 for x in range(10)]
// Java:
IntStream.range(0, 10).map(x -> x * x).boxed().collect(Collectors.toList());
// Python: [x for x in range(10) if x % 2 == 0]
// Java:
IntStream.range(0, 10).filter(x -> x % 2 == 0).boxed().collect(Collectors.toList());
1.8 常用方法速查
python
nums = [3, 1, 4, 1, 5, 9, 2, 6]
len(nums) # 8,长度
nums.sort() # 原地排序:[1, 1, 2, 3, 4, 5, 6, 9]
sorted(nums) # 返回新列表,不修改原列表
nums.reverse() # 原地反转
nums.count(1) # 2,元素1出现了几次
nums.index(5) # 4,元素5第一次出现的索引
3 in nums # True,是否包含(O(n)时间复杂度)
min(nums) # 1
max(nums) # 9
sum(nums) # 31
1.9 常见坑点/面试题
append()vsextend():append加一个元素(整体放入),extend加一批元素(逐个放入)。list.append([1,2])结果是[..., [1,2]],list.extend([1,2])结果是[..., 1, 2]。remove()只删第一个匹配 :要删所有匹配元素,用列表推导式或filter()。- 切片左闭右开 :和
range()一样,nums[1:4]取索引1、2、3。 - 列表推导式 vs
map()/filter():推导式更Pythonic,性能也更好(C层面实现)。 - 默认参数的坑 :
def func(lst=[])是共享同一个列表!必须用def func(lst=None)。
二、元组 tuple
2.1 基本介绍
元组是不可变序列 ,创建后不能修改。对应Java中 List.of() 返回的不可变列表,但Python的元组更轻量、更常用。
python
# 创建元组
point = (3, 4)
rgb = (255, 128, 0)
single = (42,) # 单元素元组必须有逗号!
empty = () # 空元组
from_list = tuple([1, 2, 3]) # 从列表转换
# 注意:(42) 不是元组,是整数42!必须有逗号:(42,)
print(type((42))) # <class 'int'>
print(type((42,))) # <class 'tuple'>
2.2 不可变性
python
point = (3, 4)
# point[0] = 5 # TypeError! 元组不可修改
# 但可以"重新赋值"(创建新元组)
point = (5, 6) # 这不是修改,是创建新对象
# 嵌套元组:外层不可变,内层如果可变则可以改
nested = ([1, 2], [3, 4])
# nested[0] = [5, 6] # TypeError! 不能替换元组的元素
nested[0].append(3) # 合法!修改的是列表本身
print(nested) # ([1, 2, 3], [3, 4])
2.3 解包(Unpacking)
解包是元组最强大的特性之一,可以优雅地一次性赋值多个变量。
python
# 基本解包
a, b = (1, 2)
print(a, b) # 1 2
# 交换变量(不需要临时变量!)
x, y = 10, 20
x, y = y, x # x=20, y=10,Java里需要tmp变量
# 星号解包(收集剩余元素为列表)
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
# 忽略某些值(用_)
name, _, age = ("蓝胖", "male", 22)
print(name, age) # 蓝胖 22
# 函数返回多值(实际返回的是元组)
def get_min_max(lst):
return min(lst), max(lst)
lo, hi = get_min_max([3, 1, 4, 1, 5, 9])
print(lo, hi) # 1 9
java
// Java 对比:没有解包,需要手动取值
// 交换变量
int tmp = x; x = y; y = tmp;
// 返回多值:需要定义Pair/Record类
public record MinMax(int min, int max) {}
MinMax result = getMinMax(list);
int lo = result.min();
int hi = result.max();
2.4 元组的常见用途
python
# 1. 函数返回多值
def divide(a, b):
return a // b, a % b # 返回商和余数(元组)
quotient, remainder = divide(17, 5) # 3, 2
# 2. 字典的键(列表不行!)
locations = {}
locations[(35, 139)] = "东京" # 用坐标做键
locations[(40, -74)] = "纽约"
# 3. 集合的元素(列表不行!)
points = {(0, 0), (1, 1), (2, 2)} # 集合元素必须可哈希
# 4. 命名元组(带字段名的元组)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
2.5 常见坑点/面试题
- 单元素元组必须加逗号 :
(42,)是元组,(42)是整数。面试常考。 - 元组不可变,但包含可变对象时可以改 :
t = ([1, 2],)中t[0].append(3)是合法的。 - 元组可以作为字典的键和集合的元素,列表不行:因为元组可哈希,列表不可哈希。
- 元组比列表更快:创建速度快、占用内存小。如果数据不需要修改,优先用元组。
三、字典 dict
3.1 基本介绍
字典是键值对集合 ,有序(Python 3.7+保证插入顺序)、可变 ,对应Java的 LinkedHashMap(有序版HashMap)。
python
# 创建字典
student = {
"name": "蓝胖",
"age": 22,
"score": 89.5
}
# 从键值对列表创建
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs) # {"a": 1, "b": 2, "c": 3}
# 空字典
empty = {} # 注意:{} 是空字典,不是空集合!空集合用 set()
java
// Java 对比
Map<String, Object> student = new LinkedHashMap<>();
student.put("name", "蓝胖");
student.put("age", 22);
student.put("score", 89.5);
3.2 增与改
python
student = {"name": "蓝胖", "age": 22}
# 新增/修改(key存在则修改,不存在则新增)
student["grade"] = "A" # 新增
student["age"] = 23 # 修改
# update:批量更新
student.update({"age": 24, "city": "北京"})
# setdefault:key不存在时才设置(有默认值的get)
student.setdefault("email", "no@email.com") # 不存在,设置默认值
student.setdefault("name", "匿名") # 已存在,不修改,返回"蓝胖"
3.3 删
python
student = {"name": "蓝胖", "age": 22, "grade": "A"}
# 按key删除
del student["grade"] # 删除 "grade" 键值对
# del student["xxx"] # key不存在报 KeyError!
# pop:删除并返回值(可设默认值避免报错)
age = student.pop("age") # 返回22
city = student.pop("city", "未知") # key不存在,返回"未知"
# popitem:删除最后插入的键值对(LIFO)
last = student.popitem() # 返回 ("name", "蓝胖")
# clear:清空
student.clear() # {}
3.4 查
python
student = {"name": "蓝胖", "age": 22}
# 直接取值(key不存在报 KeyError!)
name = student["name"]
# get:安全取值(key不存在返回None或默认值)
age = student.get("age") # 22
phone = student.get("phone") # None
phone = student.get("phone", "无") # "无"
# in:判断key是否存在(O(1)时间复杂度)
print("name" in student) # True
print("phone" in student) # False
3.5 遍历
python
student = {"name": "蓝胖", "age": 22, "city": "北京"}
# 遍历key
for key in student:
print(key)
# 遍历value
for value in student.values():
print(value)
# 遍历key-value(最常用)
for key, value in student.items():
print(f"{key}: {value}")
# 字典推导式
squared = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 带条件的推导式
even_squares = {x: x**2 for x in range(10) if x % 2 == 0}
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
# 键值对翻转
original = {"a": 1, "b": 2, "c": 3}
flipped = {v: k for k, v in original.items()}
# {1: "a", 2: "b", 3: "c"}
java
// Java 对比
for (Map.Entry<String, Object> entry : student.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
3.6 常用方法速查
python
d = {"a": 1, "b": 2, "c": 3}
d.keys() # dict_keys(['a', 'b', 'c'])
d.values() # dict_values([1, 2, 3])
d.items() # dict_items([('a', 1), ('b', 2), ('c', 3)])
len(d) # 3
"a" in d # True
d.get("a") # 1
d.get("z", 0) # 0(默认值)
d.copy() # 浅拷贝
d.update({"d": 4}) # 批量更新
d.setdefault("e", 5) # 不存在则设置
3.7 常见坑点/面试题
{}是空字典,不是空集合 :创建空集合必须用set()。- 字典的key必须是不可变类型(可哈希):字符串、数字、元组可以做key,列表不行。
- Python 3.7+ 字典保持插入顺序:面试时说明版本。之前版本的字典是无序的。
d[key]vsd.get(key):前者key不存在报KeyError,后者返回None。推荐用get()更安全。- 浅拷贝陷阱 :
d.copy()是浅拷贝,嵌套的可变对象仍然共享引用。深拷贝用copy.deepcopy()。
python
# 浅拷贝陷阱
original = {"a": [1, 2, 3]}
shallow = original.copy()
shallow["a"].append(4)
print(original["a"]) # [1, 2, 3, 4],原始字典也被修改了!
# 深拷贝
import copy
original = {"a": [1, 2, 3]}
deep = copy.deepcopy(original)
deep["a"].append(4)
print(original["a"]) # [1, 2, 3],不受影响
四、集合 set
4.1 基本介绍
集合是无序、不可重复 的元素集合,对应Java的 HashSet。主要用途:去重 和集合运算。
python
# 创建集合
s = {1, 2, 3, 4, 5}
from_list = set([1, 1, 2, 2, 3, 3]) # {1, 2, 3},自动去重
empty = set() # 空集合(注意:{}是空字典!)
# 注意:创建空集合只能用 set(),{} 创建的是空字典
print(type({})) # <class 'dict'>
print(type(set())) # <class 'set'>
java
// Java 对比
Set<Integer> s = new HashSet<>(Set.of(1, 2, 3, 4, 5));
Set<Integer> fromList = new HashSet<>(List.of(1, 1, 2, 2, 3, 3)); // 去重
4.2 去重
集合最实用的功能就是去重,一行代码搞定。
python
# 列表去重
nums = [1, 1, 2, 2, 3, 3, 4, 4, 5]
unique = list(set(nums)) # [1, 2, 3, 4, 5](顺序可能变!)
# 保持顺序的去重(Python 3.7+ 字典保持插入顺序)
unique_ordered = list(dict.fromkeys(nums)) # [1, 2, 3, 4, 5],保持原顺序
java
// Java 对比
List<Integer> unique = new ArrayList<>(new HashSet<>(nums)); // 不保序
// 保序:LinkedHashSet
List<Integer> ordered = new ArrayList<>(new LinkedHashSet<>(nums));
4.3 增删操作
python
s = {1, 2, 3}
# 添加
s.add(4) # {1, 2, 3, 4}
s.update([5, 6, 7]) # {1, 2, 3, 4, 5, 6, 7},批量添加
# 删除
s.remove(3) # 删除3,不存在则报 KeyError
s.discard(100) # 删除100,不存在也不报错(安全删除)
s.pop() # 随机删除并返回一个元素
s.clear() # 清空
4.4 集合运算(面试重点!)
集合运算在算法题和面试中经常用到:
python
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}
# 交集:两个集合都有的元素
print(a & b) # {4, 5}
print(a.intersection(b))
# 并集:所有元素(去重)
print(a | b) # {1, 2, 3, 4, 5, 6, 7, 8}
print(a.union(b))
# 差集:a有但b没有的
print(a - b) # {1, 2, 3}
print(a.difference(b))
# 对称差集:不同时属于两个集合的元素(交集的补集)
print(a ^ b) # {1, 2, 3, 6, 7, 8}
print(a.symmetric_difference(b))
# 子集/超集判断
c = {1, 2}
print(c <= a) # True,c是a的子集
print(c < a) # True,c是a的真子集
print(a >= c) # True,a是c的超集
java
// Java 对比
Set<Integer> a = new HashSet<>(Set.of(1, 2, 3, 4, 5));
Set<Integer> b = new HashSet<>(Set.of(4, 5, 6, 7, 8));
// 交集
Set<Integer> intersection = new HashSet<>(a);
intersection.retainAll(b);
// 并集
Set<Integer> union = new HashSet<>(a);
union.addAll(b);
// 差集
Set<Integer> diff = new HashSet<>(a);
diff.removeAll(b);
// Java没有内置对称差集运算符,需要手动实现
4.5 实际应用示例
python
# 场景1:找两个列表的公共元素
list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]
common = list(set(list1) & set(list2)) # [4, 5]
# 场景2:判断两个列表是否有交集
has_common = bool(set(list1) & set(list2)) # True
# 场景3:字符串去重
text = "hello world"
unique_chars = set(text) # {'h', 'e', 'l', 'o', ' ', 'w', 'r', 'd'}
print(len(unique_chars)) # 不同字符数
# 场景4:集合推导式
squares = {x**2 for x in range(-3, 4)} # {0, 1, 4, 9}(-3和3的平方相同,自动去重)
4.6 常见坑点/面试题
set()创建空集合,{}创建空字典:这个坑必考。- 集合是无序的:不能通过索引访问,转列表后顺序不确定。
- 集合元素必须可哈希:列表、字典、集合不能作为集合元素。元组可以(只要元组内没有可变对象)。
remove()vsdiscard():remove()元素不存在报KeyError,discard()不报错。推荐用discard()。- 去重会丢失顺序 :如果需要保持原始顺序,用
list(dict.fromkeys(lst))。 - frozenset :不可变集合,可以作为字典的键和其他集合的元素。
frozenset({1, 2, 3})。
五、四种数据结构对比
| 特性 | list | tuple | dict | set |
|---|---|---|---|---|
| 有序性 | ✅ 有序 | ✅ 有序 | ✅ 有序(3.7+) | ❌ 无序 |
| 可变性 | ✅ 可变 | ❌ 不可变 | ✅ 可变 | ✅ 可变 |
| 可重复 | ✅ 可重复 | ✅ 可重复 | key唯一 | ❌ 不可重复 |
| 索引访问 | ✅ | ✅ | ❌ | ❌ |
| Java对应 | ArrayList | Tuple/不可变List | LinkedHashMap | HashSet |
| 字面量语法 | [1, 2, 3] |
(1, 2, 3) |
{"k": "v"} |
{1, 2, 3} |
| 典型用途 | 通用容器 | 不可变数据、函数多返回值 | 键值映射 | 去重、集合运算 |
🗺️ 思维导图速览
Python数据结构
├── 列表 list(≈ Java ArrayList)
│ ├── 增:append() / extend() / insert()
│ ├── 删:remove() / pop() / del / clear()
│ ├── 改:索引赋值 / 切片赋值
│ ├── 查:索引 / 切片[start:end:step]
│ │ ├── 负数索引:-1是最后一个
│ │ ├── nums[::-1] 反转
│ │ └── 切片不会越界
│ ├── 列表推导式:[expr for x in iter if cond]
│ └── 坑:默认参数不要用[]
├── 元组 tuple(不可变序列)
│ ├── 创建:(1, 2) 单元素必须(1,)
│ ├── 解包:a, b = (1, 2)
│ ├── 交换:x, y = y, x
│ ├── 星号解包:first, *rest = [1,2,3]
│ └── 用途:函数多返回值、字典键、集合元素
├── 字典 dict(≈ Java LinkedHashMap)
│ ├── 创建:{"k": "v"} 空集合用set()不是{}
│ ├── 增改:d[key]=value / update() / setdefault()
│ ├── 删:del / pop(key, default) / popitem()
│ ├── 查:d[key](报错) / d.get(key)(安全)
│ ├── 遍历:keys() / values() / items()
│ ├── 推导式:{k: v for k, v in pairs}
│ └── 坑:浅拷贝 / key必须可哈希
├── 集合 set(≈ Java HashSet)
│ ├── 创建:{1,2,3} / set(list)
│ ├── 去重:list(set(list))
│ ├── 保序去重:list(dict.fromkeys(list))
│ ├── 集合运算:
│ │ ├── 交集 & 并集 | 差集 - 对称差集 ^
│ │ └── 子集 <= 超集 >=
│ ├── 集合推导式:{expr for x in iter}
│ └── 坑:{}是空字典 / 元素必须可哈希
└── 对比速记
├── 有序可变 → list
├── 有序不可变 → tuple
├── 键值对 → dict
└── 去重/集合运算 → set
📝 写在最后
学习建议
- 列表推导式必须练熟 :这是Python最标志性的语法之一,面试手写代码经常出现。先掌握基本形式
[x for x in range(n)],再加条件[x for x in range(n) if x%2==0],最后挑战嵌套。 - 切片要形成肌肉记忆 :
nums[::-1]反转、nums[::2]跳步取、nums[-3:]取末尾------这些操作在算法题和日常编码中极高频。 - 字典用
get()代替[]:养成用d.get(key, default)的习惯,避免KeyError。面试时这体现了你对Python安全编码的理解。 - 集合去重是基本功 :但要注意去重后顺序会乱,需要保序就用
dict.fromkeys()。 - 元组解包要会用 :
a, b = b, a交换变量、first, *rest = list拆分列表、函数返回多值------这些写法在面试中很加分。
面试高频问题速答
Q:list和tuple的区别?
list可变,tuple不可变。tuple可以作为字典的key和集合的元素(因为可哈希),list不行。tuple创建速度比list快,占用内存更少。如果数据不需要修改,优先用tuple。
Q:Python字典和Java HashMap有什么区别?
Python 3.7+的字典保持插入顺序(类似Java的LinkedHashMap),Java HashMap不保证顺序。Python字典用
get()安全取值,Java直接get()返回null。Python字典推导式{k:v for ...}比Java的Collectors.toMap()更简洁。
Q:列表推导式和Stream API的区别?
语义上类似,但推导式更简洁、更Pythonic。推导式直接返回列表,Stream需要
collect()。性能上推导式通常更快(C层面实现),Stream支持并行(parallelStream()),推导式不支持。推导式可以嵌套,Stream用flatMap()。
Q:set([1,1,2,2,3]) 的结果是什么?
{1, 2, 3},集合自动去重。注意集合是无序的(虽然小整数碰巧看起来有序),不要依赖顺序。如果需要保序去重,用list(dict.fromkeys([1,1,2,2,3]))。
Q:a & b 和 a.intersection(b) 一样吗?
结果一样,都是求交集。
&是运算符形式,intersection()是方法形式。运算符形式要求两边都是set,方法形式可以接受任何可迭代对象。推荐用运算符形式,更简洁。