📦 第一部分:核心容器 - 选择正确的工具
Python 提供了多种内置数据结构。新手往往只会用列表,但专业开发者会根据数据特性和操作需求选择最合适的数据结构。
1.1 列表 (list):有序的可变序列
🎯 适用场景:
需要存储一组有序数据
需要频繁修改(增删改)
允许重复元素
# ✅ 创建列表
fruits = ["apple", "banana", "orange"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True] # 列表可以包含不同类型
# ✅ 增删改查操作
fruits.append("grape") # 尾部添加:O(1)
fruits.insert(0, "pear") # 指定位置插入:O(n)
fruits.remove("banana") # 删除第一个匹配值:O(n)
last = fruits.pop() # 弹出并删除最后一个:O(1)
first = fruits.pop(0) # 弹出并删除第一个:O(n)
# ✅ 访问与修改
print(fruits[0]) # 获取第一个元素
fruits[1] = "watermelon" # 修改指定位置
# ✅ 切片 (Slicing) - Python 的灵魂特性
# 语法:[start:stop:step]
# start: 起始索引 (包含),默认 0
# stop: 结束索引 (不包含),默认列表长度
# step: 步长 (默认 1),负数表示反向
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5]) # [2, 3, 4] - 索引 2 到 4
print(numbers[:3]) # [0, 1, 2] - 前 3 个
print(numbers[7:]) # [7, 8, 9] - 从索引 7 到最后
print(numbers[::2]) # [0, 2, 4, 6, 8] - 每隔一个取一个
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - 反转列表
print(numbers[-3:]) # [7, 8, 9] - 最后 3 个 (负数索引)
🚀 列表推导式 (List Comprehension) - 现代写法
列表推导式是 Python 最优雅的特性之一,用一行代码完成"循环 + 过滤 + 收集"。
# ❌ 旧式写法:5 行代码
squares = []
for i in range(5):
squares.append(i ** 2)
# 结果:[0, 1, 4, 9, 16]
# ✅ 推导式写法:1 行代码,语义清晰
squares = [i ** 2 for i in range(5)]
# ✅ 带条件过滤的推导式
evens = [i for i in range(10) if i % 2 == 0]
# 结果:[0, 2, 4, 6, 8]
# ✅ 带条件表达式的推导式 (三元运算符)
labels = ["偶数" if i % 2 == 0 else "奇数" for i in range(5)]
# 结果:["偶数", "奇数", "偶数", "奇数", "偶数"]
# ✅ 嵌套推导式 (二维列表)
matrix = [[i * j for j in range(3)] for i in range(3)]
# 结果:[[0, 0, 0], [0, 1, 2], [0, 2, 4]]
💡 最佳实践:推导式适合简单逻辑。如果逻辑复杂(超过一层嵌套或多个条件),请拆分成普通循环,保持可读性。
1.2 字典 (dict):键值对映射
🎯 适用场景:
需要通过键 (key) 快速查找值 (value)
表示一个对象的结构化数据
键必须是不可变类型 (str, int, tuple 等)
# ✅ 创建字典
user = {
"name": "Alice",
"age": 25,
"email": "alice@example.com"
}
# ✅ 访问与修改
print(user["name"]) # 获取值:Alice
user["age"] = 26 # 修改值
user["city"] = "Beijing" # 添加新键值对
# ✅ 安全访问:避免 KeyError
# ❌ 直接访问不存在的键会报错
# print(user["phone"]) # KeyError: 'phone'
# ✅ 推荐:使用 .get() 提供默认值
phone = user.get("phone", "未提供") # "未提供"
email = user.get("email", "N/A") # "alice@example.com"
# ✅ 遍历字典
for key, value in user.items():
print(f"{key}: {value}")
# ✅ 字典推导式
# 将两个列表合并为字典
keys = ["a", "b", "c"]
values = [1, 2, 3]
my_dict = {k: v for k, v in zip(keys, values)}
# 结果:{'a': 1, 'b': 2, 'c': 3}
# 过滤字典
scores = {"Alice": 90, "Bob": 85, "Charlie": 95}
passed = {name: score for name, score in scores.items() if score >= 90}
# 结果:{'Alice': 90, 'Charlie': 95}
💡 性能提示:字典的查找、插入、删除操作平均时间复杂度都是 O(1),非常适合需要快速查找的场景。
1.3 元组 (tuple):有序的不可变序列
🎯 适用场景:
数据不应该被修改(如配置项、坐标点)
作为字典的键(因为元组是不可变的)
函数返回多个值
# ✅ 创建元组
point = (10, 20)
colors = "red", "green", "blue" # 括号可省略
single = (1,) # 单元素元组必须加逗号,否则是普通括号
# ✅ 不可变性:一旦创建,不能修改
# point[0] = 0 # ❌ 报错:TypeError: 'tuple' object does not support item assignment
# ✅ 解包 (Unpacking) - 元组的灵魂用法
x, y = point # x=10, y=20
# ✅ 交换变量值的经典写法
a, b = 1, 2
a, b = b, a # a=2, b=1
# ✅ 函数返回多个值
def get_user_info():
return "Alice", 25, "Beijing"
name, age, city = get_user_info()
# ✅ 忽略不需要的值 (使用 _ 占位符)
def get_coordinates():
return 10, 20, 30 # x, y, z
x, _, z = get_coordinates() # 只关心 x 和 z
💡 命名元组 (namedtuple):如果需要给元组的每个位置起名字,可以使用
collections.namedtuple,兼具元组的轻量性和字典的可读性。
1.4 集合 (set):无序且不重复
🎯 适用场景:
去重:快速移除列表中的重复元素
数学集合运算:交集、并集、差集
快速成员检查:
x in my_set比x in my_list快得多
# ✅ 创建集合
ids = [1, 2, 2, 3, 3, 3, 4]
unique_ids = set(ids) # {1, 2, 3, 4} - 自动去重
# ✅ 集合运算
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # 交集:{3, 4}
print(a | b) # 并集:{1, 2, 3, 4, 5, 6}
print(a - b) # 差集:{1, 2} (在 a 中但不在 b 中)
print(a ^ b) # 对称差集:{1, 2, 5, 6} (只在其中一个集合中)
# ✅ 成员检查 - 集合比列表快
# 列表:需要遍历,时间复杂度 O(n)
# 集合:哈希查找,时间复杂度 O(1)
valid_users = {"admin", "alice", "bob"}
if "alice" in valid_users: # 瞬间完成
print("用户存在")
# ✅ 集合推导式
squares = {i ** 2 for i in range(5)} # {0, 1, 4, 9, 16}
💡 重要区别:
[]创建列表,{}创建字典,set()创建空集合(因为{}是空字典)集合的元素必须是可哈希的(不可变类型),不能包含列表或字典
🔍 数据结构选择指南
✨ 第二部分:现代类型提示 - 心智与语法
这是本章的重中之重。很多初学者对类型提示有误解,认为它是"束缚"。实际上,它是现代 Python 开发的**"智能辅助轮"**。
2.1 心智建设:类型提示是给谁看的?
请务必记住这个核心概念:
类型提示 (Type Hints) 是给编辑器 (IDE) 和 协作者 看的,Python 解释器在运行时会直接忽略它。
❌ 常见误解
def add(a: int, b: int) -> int:
return a + b
# 误解:以为这样能阻止传入字符串
add(1, "2") # ❌ 以为会报错?
真相:Python 依然会运行这段代码,直到运行时抛出 TypeError。类型提示不会在运行时阻止错误。
✅ 正确认知
类型提示的真正价值:
文档化:函数签名即文档,无需再看注释猜测参数类型
# 不用看文档就知道这个函数要什么 def process_order(order_id: int, amount: float, user_id: str) -> bool: ...智能补全:IDE 知道
user是dict,当你输入user.时,它会提示字典的方法def greet(user: dict[str, str]): # 输入 user. 时,IDE 会提示 keys(), values(), get() 等 print(user.get("name"))重构安全:修改代码时,静态检查工具(如
mypy)能帮你发现潜在的类型错误$ mypy my_script.py my_script.py:10: error: Argument 1 to "add" has incompatible type "str"; expected "int"团队协作:新同事接手代码时,类型提示是最好的"使用说明书"
2.2 现代语法 (Python 3.9/10+):告别 typing 模块
Python 3.9 和 3.10 引入了重大语法简化。请忘记 from typing import ... 的旧写法,直接使用内置类型。
📊 语法对比表
💡 记忆技巧
以前需要大写首字母 (
List) 还要导入,现在直接用你平时创建变量的小写类型 (list) 即可\|符号就像岔路口,表示"可以是左边,也可以是右边"None就是 Python 里的"空"(类似其他语言的null)
2.3 实战详解:在哪里写类型提示?
类型提示主要写在三个地方:变量、函数参数、函数返回值。
2.3.1 变量标注
在变量名后面加冒号 : 和类型。
# ✅ 基础类型
count: int = 0
price: float = 9.99
name: str = "Alice"
is_active: bool = True
# ✅ 容器类型
tags: list[str] = ["python", "coding"] # 字符串列表
scores: dict[str, int] = {"math": 90} # 键是字符串,值是整数
point: tuple[int, int] = (10, 20) # 坐标元组
unique_ids: set[int] = {1, 2, 3} # 整数集合
# ✅ 复杂组合
# 一个列表,里面每个元素都是"字符串或整数"
mixed_list: list[str | int] = ["a", 1, "b", 2]
# ✅ 可空类型
nickname: str | None = None # 可能是字符串,也可能是空
2.3.2 函数参数与返回值 (最有价值的地方)
语法结构:
def 函数名 (参数名:类型,参数名:类型) -> 返回值类型:
# ✅ 示例:计算商品总价
def calculate_total(price: float, quantity: int, tax_rate: float = 0.1) -> float:
"""
计算含税总价
Args:
price: 商品单价 (浮点数)
quantity: 购买数量 (整数)
tax_rate: 税率,默认 0.1 (浮点数)
Returns:
float: 含税总价
"""
subtotal = price * quantity
return subtotal * (1 + tax_rate)
# ✅ 调用时,IDE 会提示参数类型
total = calculate_total(19.99, 2) # IDE 提示 price 需要 float, quantity 需要 int
2.3.3 没有返回值怎么办?
如果函数只是执行操作,不返回数据,使用 None。
def log_message(message: str, level: str = "INFO") -> None:
"""记录日志,不返回任何值"""
print(f"[{level}] {message}")
# 这里不需要写 return,或者写 return 而不带值
2.4 体验反馈:让 IDE 成为你的助手
类型提示的强大,必须配合现代编辑器才能体现。请按照以下步骤亲自体验:
🔧 环境准备
使用 VS Code 或 PyCharm
VS Code 用户:安装 Python 和 Pylance 插件
确保
settings.json中启用了类型检查:{ "python.analysis.typeCheckingMode": "basic" }
🎯 体验步骤
步骤 1:感受智能补全
def greet(name: str) -> str:
return f"Hello {name}"
message = greet("Alice")
# 输入 message. 时,IDE 会自动提示字符串方法:upper, lower, split...
# 因为 IDE 知道 message 是 str 类型!
步骤 2:感受错误预警
def add(a: int, b: int) -> int:
return a + b
# 故意传一个字符串
result = add(1, "2") # 👀 注意看这里
现象:你会看到
"2"下面出现了黄色或红色的波浪线提示内容:
Argument of type "str" cannot be assigned to parameter "b" of type "int"含义:编辑器在告诉你:"嘿,这里定义的是整数,你传了字符串,可能会出错哦!"
步骤 3:感受容器类型的智能提示
def process_users(users: list[dict[str, str]]) -> None:
for user in users:
# 输入 user. 时,IDE 知道 user 是 dict[str, str]
# 会提示 get(), keys(), values() 等字典方法
name = user.get("name") # IDE 知道返回值是 str | None
💡 核心价值:类型提示 + 智能编辑器 = 把错误消灭在运行之前,而不是等到用户反馈才发现问题。
2.5 常见场景的类型注解速查表
from datetime import datetime
from collections.abc import Callable # 用于函数类型
# 🔹 基本类型
age: int = 25
price: float = 19.99
name: str = "Alice"
is_valid: bool = True
# 🔹 容器类型
ids: list[int] = [1, 2, 3]
user: dict[str, str] = {"name": "Alice"}
tags: set[str] = {"python", "coding"}
point: tuple[int, int] = (10, 20)
matrix: list[list[float]] = [[1.0, 2.0], [3.0, 4.0]] # 二维列表
# 🔹 联合与可选
user_id: int | str = 1001 # 可能是数字也可能是字符串
nickname: str | None = None # 可能是字符串,也可能是空
status: "pending" | "active" | "closed" = "pending" # 字面量类型 (Python 3.11+)
# 🔹 函数类型
Callback = Callable[[str], bool] # 接收字符串,返回布尔值的函数
def register_handler(name: str, callback: Callback) -> None:
...
# 🔹 泛型函数
def first_item[T](items: list[T]) -> T | None:
"""返回列表的第一个元素,如果为空则返回 None"""
return items[0] if items else None
# 🔹 类类型
class User:
def __init__(self, name: str, age: int):
self.name: str = name
self.age: int = age
def greet_user(user: User) -> str:
return f"Hello {user.name}"
🛡️ 第三部分:最佳实践与避坑指南
3.1 何时使用类型提示?(优先级指南)
3.2 不要依赖类型提示做运行时验证
再次强调:类型提示不会在运行时阻止错误。
# ❌ 错误想法:以为这样能阻止传入字符串
def add(a: int, b: int) -> int:
return a + b
add(1, "2") # Python 依然会运行,直到内部抛出 TypeError
✅ 正确做法:
如果需要运行时验证,使用逻辑判断:
def add(a: int, b: int) -> int: if not isinstance(a, int) or not isinstance(b, int): raise TypeError("参数必须是整数") return a + b或者使用专业库如 Pydantic (Web 开发常用):
from pydantic import BaseModel, validator class Order(BaseModel): amount: float @validator('amount') def amount_must_be_positive(cls, v): if v <= 0: raise ValueError('金额必须为正') return v
3.3 保持一致性:项目级规范
要么全加,要么大部分加:不要混用风格,会让代码显得杂乱
使用配置文件:现代项目通常在
pyproject.toml中配置类型检查:[tool.mypy] python_version = "3.10" warn_return_any = true warn_unused_configs = true [tool.ruff] select = ["E", "F", "I"] # 启用错误、未使用导入等检查集成到开发流程:在 CI/CD 中使用
mypy或pyright强制检查
3.4 数据结构选择决策树
需要存储数据?
├─ 需要按键快速查找? → dict
├─ 需要保持顺序 + 允许修改? → list
├─ 数据不应该被修改? → tuple
├─ 需要去重或集合运算? → set
└─ 不确定? → 先用 list,后续再优化
🎯 本章实战练习
练习 1:数据清洗脚本
# 任务:
# 1. 创建一个包含重复整数的列表
# 2. 使用 set 去重,再转回 list 并排序
# 3. 使用列表推导式计算每个数的平方
# 4. 为所有变量和函数添加现代类型提示
def clean_and_process(data: list[int]) -> list[int]:
"""去重、排序、计算平方"""
unique_sorted: list[int] = sorted(set(data))
squared: list[int] = [x ** 2 for x in unique_sorted]
return squared
# 测试
if __name__ == "__main__":
raw_data: list[int] = [3, 1, 4, 1, 5, 9, 2, 6, 5]
result: list[int] = clean_and_process(raw_data)
print(f"处理结果:{result}")
# 预期输出:[1, 4, 9, 16, 25, 36, 81]
练习 2:用户信息管理系统
# 任务:
# 1. 使用 dict 存储用户信息
# 2. 编写更新函数,使用守卫子句检查参数
# 3. 观察 IDE 的类型提示效果
def update_user(
user: dict[str, str | int],
key: str,
value: str | int
) -> None:
"""安全地更新用户信息"""
# 守卫子句:检查 key 是否允许更新
allowed_keys: set[str] = {"name", "age", "email"}
if key not in allowed_keys:
raise ValueError(f"不允许更新字段:{key}")
# 类型检查 (运行时)
if key == "age" and not isinstance(value, int):
raise TypeError("age 必须是整数")
user[key] = value
# 测试
if __name__ == "__main__":
user_info: dict[str, str | int] = {
"name": "Alice",
"age": 25,
"email": "alice@example.com"
}
update_user(user_info, "age", 26) # ✅ 正常
# update_user(user_info, "password", "123") # ❌ 会抛出 ValueError
print(user_info)
练习 3:类型错误实验 (感受工具的价值)
# 任务:故意写一个类型不匹配的代码,观察 IDE 警告
def calculate_discount(price: float, discount: float) -> float:
"""计算折后价格"""
return price * (1 - discount)
# 故意传入错误类型
result = calculate_discount("100", 0.1) # 👀 看这里的警告
# 运行时会报错:
# TypeError: unsupported operand type(s) for *: 'str' and 'float'
# 但 IDE 会在你写代码时就标红,提前发现问题
📝 本章总结
✅ 你已掌握
四大核心容器的使用场景与操作技巧
列表推导式的优雅写法
现代类型提示语法 (
list[int],int \| str)类型提示的真正价值:给工具和协作者看,不是给解释器看
最佳实践:何时加类型、如何保持一致性
🔑 核心心智
"类型提示不是束缚,而是安全网。它让代码更自文档化,让重构更安心,让协作更顺畅。"