Python 数据结构详解:序列与集合
详细讲解了 Python 核心数据结构,包括字符串、列表、字典、集合和元组。重点阐述了可变与不可变对象的区别及其影响,涵盖了索引切片、常用方法、推导式及浅拷贝陷阱等关键知识点。通过代码示例展示了各数据结构的创建、操作及最佳实践,帮助开发者高效组织和管理数据。

详细讲解了 Python 核心数据结构,包括字符串、列表、字典、集合和元组。重点阐述了可变与不可变对象的区别及其影响,涵盖了索引切片、常用方法、推导式及浅拷贝陷阱等关键知识点。通过代码示例展示了各数据结构的创建、操作及最佳实践,帮助开发者高效组织和管理数据。

上一章我们学会了 Python 的基本语法,能定义变量、处理简单的数字和字符串。但现实世界的数据远不止单个值——比如一个班级的学生名单、商品的库存信息、网站的访问日志……这些都需要用数据结构来组织和存储。
Python 内置了多种强大的数据结构,它们就像不同形状的容器,有的能装一串有序的东西(列表),有的能通过名字快速查找(字典),有的能保证元素唯一(集合)。掌握它们,你的编程能力将瞬间提升一个档次。
本章我们就来逐个击破:字符串(深入)、列表、字典、集合、元组,并理解一个核心概念——可变与不可变。
在深入具体数据结构之前,先搞清楚一个关键问题:对象能不能被修改?
# 不可变:整数
a = 10
print(id(a)) # 某个地址
a += 1
print(id(a)) # 地址变了,因为创建了新整数
# 可变:列表
lst = [1, 2, 3]
print(id(lst)) # 地址 A
lst.append(4)
print(id(lst)) # 地址 A(没变)
理解这个对后续学习至关重要,因为可变对象的方法往往没有返回值(就地修改),而不可变对象的方法会返回新对象。
第一章我们学会了字符串的基本操作,现在给它来一次'深度体检'。记住:字符串是不可变序列,所以所有'修改'操作都是返回新字符串。
字符串里的每个字符都有位置(索引),从 0 开始。
s = "Python"
print(s[0]) # P
print(s[-1]) # n(负数表示从右往左)
切片:[start:stop:step] 截取子串(含 start,不含 stop)
s = "Hello, Python"
print(s[7:13]) # Python
print(s[:5]) # Hello(省略 start 表示从头)
print(s[7:]) # Python(省略 stop 表示到尾)
print(s[::2]) # Hlo yhn(步长为 2)
print(s[::-1]) # nohtyP ,olleH(反转字符串)
| 方法 | 作用 | 示例 |
|---|---|---|
split() | 按分隔符拆成列表 | "a,b,c".split(",") → ['a','b','c'] |
join() | 将列表合并为字符串 | "-".join(['a','b','c']) → "a-b-c" |
replace() | 替换子串 | "Hello world".replace("world","Python") → "Hello Python" |
strip() | 去除两端空白字符 | " hello ".strip() → "hello" |
upper()/lower() | 大小写转换 | "Python".upper() → "PYTHON" |
startswith()/endswith() | 判断开头/结尾 | "hello.py".endswith(".py") → True |
find()/index() | 查找子串位置 | "hello".find("l") → 2(找不到返回 -1);index() 找不到抛异常 |
count() | 统计出现次数 | "abracadabra".count("a") → 5 |
冷知识:split() 如果不给参数,会按任意空白字符分割(空格、换行、制表符),并且会压缩连续空白。而 split(' ') 则只按单个空格分割。
name = "Alice"
age = 25
print("我是 %s,今年 %d 岁"%(name, age))
print("我是 {},今年 {} 岁".format(name, age))
print("我是 {name},今年 {age} 岁".format(name=name, age=age))
print(f"我是 {name},今年 {age} 岁")
print(f"3.14159 保留两位小数:{3.14159:.2f}") # 3.14
f-string 简洁直观,还能在花括号里写表达式,必学!
列表是可变序列,能装任意类型的数据(甚至可以混合类型),用方括号 [] 表示。
fruits = ["apple", "banana", "orange"]
mixed = [1, "hello", 3.14, True]
empty = [] # 空列表
list_from_str = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
列表推导式(快速生成列表的优雅写法):
squares = [x**2 for x in range(10)] # [0, 1, 4, 9, ..., 81]
evens = [x for x in range(20) if x % 2 == 0]
| 方法 | 作用 | 示例(假设 lst = [1, 2, 3]) |
|---|---|---|
append(x) | 末尾添加一个元素 | lst.append(4) → [1,2,3,4] |
extend(iterable) | 末尾添加多个元素 | lst.extend([4,5]) → [1,2,3,4,5] |
insert(i, x) | 在 i 位置插入 x | lst.insert(1, 99) → [1,99,2,3] |
remove(x) | 删除第一个值为 x 的元素 | lst.remove(2) → [1,3] |
pop([i]) | 删除并返回 i 位置元素(默认末尾) | lst.pop() → 3,列表变为 [1,2] |
clear() | 清空列表 | lst.clear() → [] |
index(x) | 返回第一个 x 的索引 | [1,2,3].index(2) → 1 |
count(x) | 统计 x 出现次数 | [1,2,2,3].count(2) → 2 |
sort() | 原地排序 | [3,1,2].sort() → [1,2,3] |
reverse() | 原地反转 | [1,2,3].reverse() → [3,2,1] |
copy() | 返回浅拷贝 | new_lst = lst.copy() |
⚠️ 注意:append 和 extend 的区别:
lst = [1, 2]
lst.append([3, 4]) # [1, 2, [3, 4]]
lst.extend([5, 6]) # [1, 2, [3, 4], 5, 6]
列表的切片不仅可读,还可赋值修改(这是可变性的体现)。
lst = [10, 20, 30, 40, 50]
print(lst[1:4]) # [20, 30, 40]
lst[1:4] = [99, 100] # 替换切片内容
print(lst) # [10, 99, 100, 50]
删除元素/切片:del
del lst[1] # 删除索引 1 的元素
del lst[1:3] # 删除切片
拼接:+ 和 *(返回新列表,不修改原列表)
print([1, 2] + [3, 4]) # [1, 2, 3, 4]
print([0] * 5) # [0, 0, 0, 0, 0]
列表的 copy() 或 [:] 是浅拷贝——只复制外层容器,内层元素还是引用。如果列表里包含可变对象(比如另一个列表),修改内层会影响拷贝。
a = [1, [2, 3]]
b = a.copy()
b[0] = 100 # 修改外层,不影响 a
b[1][0] = 999 # 修改内层,a 也被改了!
print(a) # [1, [999, 3]]
要完全独立需用 copy.deepcopy。
字典是可变映射,存储键值对,用花括号 {} 表示。键必须是不可变类型(如字符串、数字、元组),值可以是任意对象。
person = {"name": "Alice", "age": 25, "city": "New York"}
empty_dict = {}
dict_from_pairs = dict([("name", "Bob"), ("age", 30)])
dict_with_keywords = dict(name="Charlie", age=35)
字典推导式:
squares_dict = {x: x**2 for x in range(5)} # {0:0, 1:1, 2:4, 3:9, 4:16}
print(person["name"]) # Alice
print(person.get("gender", "未知")) # 键不存在返回默认值,不会抛 KeyError
person["age"] = 26 # 修改
person["gender"] = "female" # 新增键值对
person.update({"city": "Boston", "job": "engineer"}) # 批量更新
del person["gender"] # 删除指定键
age = person.pop("age") # 删除并返回该键的值
last_item = person.popitem() # 删除并返回最后一个键值对(Python 3.7+ 有序)
person.clear() # 清空
返回动态视图,会随字典变化而变化。
d = {"a": 1, "b": 2}
print(d.keys()) # dict_keys(['a', 'b'])
print(d.values()) # dict_values([1, 2])
print(d.items()) # dict_items([('a', 1), ('b', 2)])
# 常用循环
for k, v in d.items():
print(k, v)
以前字典是无序的,现在会保持插入顺序。但别依赖排序来做什么——想排序请用 sorted()。
如果想在访问不存在的键时自动生成默认值,可以用 defaultdict:
from collections import defaultdict
dd = defaultdict(int) # 默认值 0
dd["count"] += 1 # 不会报错
print(dd) # {'count': 1}
集合是可变无序集,元素必须唯一且不可变(所以不能放列表)。用花括号 {} 表示,但空集要用 set()。
s = {1, 2, 3}
empty_set = set() # 注意 {} 是空字典
from_list = set([1, 2, 2, 3]) # {1, 2, 3} 自动去重
集合推导式:
squares_set = {x**2 for x in range(5)} # {0, 1, 4, 9, 16}
| 方法 | 作用 | 示例 |
|---|---|---|
add(x) | 添加元素 | s.add(4) |
remove(x) | 删除 x,不存在抛 KeyError | s.remove(2) |
discard(x) | 删除 x,不存在不报错 | s.discard(100) |
pop() | 随机删除并返回一个元素 | s.pop() |
clear() | 清空 | s.clear() |
假设 a = {1, 2, 3},b = {2, 3, 4}
| 运算 | 符号 | 方法 | 结果 |
|---|---|---|---|
| 并集 | ` | ` | a.union(b) |
| 交集 | & | a.intersection(b) | {2, 3} |
| 差集 | - | a.difference(b) | {1} |
| 对称差 | ^ | a.symmetric_difference(b) | {1, 4} |
子集判断:
{1, 2}.issubset({1, 2, 3}) # True,也可以用 <=
{1, 2, 3}.issuperset({1, 2}) # True,也可以用 >=
如果你需要一个不能修改的集合(比如作为字典的键),可以用 frozenset:
fs = frozenset([1, 2, 3])
# fs.add(4) # 报错
元组是不可变序列,用圆括号 () 表示。一旦创建,不能增删改元素。
t = (1, 2, 3)
single = (1,) # 单元素必须加逗号,否则 (1) 就是整数 1
empty = ()
from_list = tuple([4, 5, 6])
元组的不可变是指元素的引用不可变。如果元素本身是可变对象,那它的内容还是可以变的。
t = (1, [2, 3])
t[1].append(4) # 合法!t 变成 (1, [2, 3, 4])
# t[0] = 100 # 报错,不能修改元组本身
point = (10, 20)
x, y = point # x=10, y=20
a, b, c = (1, 2, 3)
甚至可以用 * 收集多余元素:
first, *rest = (1, 2, 3, 4) # first=1, rest=[2, 3, 4]
t = (1, 2, 2, 3)
print(t.count(2)) # 2
print(t.index(3)) # 3
collections.namedtuple 可以创建带字段名的元组,像轻量级对象:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(10, 20)
print(p.x, p.y) # 10 20
| 数据结构 | 类型 | 可变? | 特点 | 常用场景 |
|---|---|---|---|---|
| 字符串 | 序列 | ❌ 不可变 | 文本处理 | 几乎所有程序 |
| 列表 | 序列 | ✅ 可变 | 万能容器,有序 | 存储任意序列 |
| 元组 | 序列 | ❌ 不可变 | 轻量,可哈希 | 固定数据,函数返回多值 |
| 字典 | 映射 | ✅ 可变 | 键值对,快速查找 | 结构化数据,缓存 |
| 集合 | 集合 | ✅ 可变 | 元素唯一,集合运算 | 去重,关系判断 |
记住:
[],元组用 (),字典用 {}(键值对),集合也用 {}(但元素单一)
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online