列表快速上手
列表(List)是 Python 中最常用的可变序列类型,用来按顺序存储一组元素。无论是保存一批数据、维护待办事项,还是处理接口返回的数组,都离不开列表。本节将从创建、索引切片、增删改查讲到排序、推导式与常见陷阱。
前置知识
阅读本节前,建议先了解:字符串基本操作,理解序列的索引与切片机制。
基础概念
列表用方括号 [] 创建,元素之间用逗号分隔。它是有序(Ordered)、可变(Mutable)、可重复(Allow Duplicates)、可异构(Heterogeneous)的容器:同一个列表可以同时存放字符串、数字甚至嵌套列表。
| 特性 | 英文 | 说明 |
|---|---|---|
| 有序 | Ordered | 元素按插入顺序保存,可用索引访问 |
| 可变 | Mutable | 可就地增删改元素 |
| 可重复 | Duplicates Allowed | 相同元素可以出现多次 |
| 异构 | Heterogeneous | 可存放不同类型的元素 |
| 引用语义 | Reference Semantics | 赋值是引用拷贝,不是值拷贝 |
语法与用法
创建与访问列表的最基本写法:
# 最简可运行示例
fruits = ["apple", "banana", "cherry"]
print(fruits) # 打印整个列表
print(fruits[0]) # 第一个元素
print(fruits[-1]) # 最后一个元素
print(len(fruits)) # 元素个数['apple', 'banana', 'cherry']
apple
cherry
3详细说明
创建列表的多种方式
# 字面量创建
empty: list[int] = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, True, None] # 异构列表
nested = [[1, 2], [3, 4], [5, 6]] # 嵌套列表
# list() 构造函数
from_string = list("abc") # 字符串 -> 字符列表
from_range = list(range(5)) # range -> 列表
from_tuple = list((1, 2, 3)) # 元组 -> 列表
# 列表推导式(推荐用于生成规律数据)
squares = [n ** 2 for n in range(1, 6)]
evens = [n for n in range(10) if n % 2 == 0]
# 重复列表
zeros = [0] * 5
repeated = ["ab"] * 3
print(empty, numbers)
print(from_string, from_range)
print(squares, evens)
print(zeros, repeated)[] [1, 2, 3, 4, 5]
['a', 'b', 'c'] [0, 1, 2, 3, 4]
[1, 4, 9, 16, 25] [0, 2, 4, 6, 8]
[0, 0, 0, 0, 0] ['ab', 'ab', 'ab']嵌套列表的乘法陷阱
[[0] * 3] * 3 会得到 3 个指向同一个内层列表的引用,修改其中一个会「连带修改」其他行。应使用推导式创建独立的内层列表。
# 错误!三行共享同一个内层列表
wrong = [[0] * 3] * 3
wrong[0][0] = 9
print("错误写法:", wrong)
# 正确:每行都是独立的新列表
right = [[0] * 3 for _ in range(3)]
right[0][0] = 9
print("正确写法:", right)错误写法: [[9, 0, 0], [9, 0, 0], [9, 0, 0]]
正确写法: [[9, 0, 0], [0, 0, 0], [0, 0, 0]]索引、切片与遍历
列表的索引与切片规则和字符串完全一致:
numbers = [10, 20, 30, 40, 50]
# 索引: 0 1 2 3 4
# 负索引: -5 -4 -3 -2 -1
print(numbers[0], numbers[-1])
print(numbers[1:4], numbers[:3], numbers[2:]) # 各种切片
print(numbers[::-1], numbers[::2]) # 反转与步长
# 切片赋值:可就地替换一段元素(长度可不同)
numbers[1:4] = [200, 300]
print(numbers)10 50
[20, 30, 40] [10, 20, 30] [30, 40, 50]
[50, 40, 30, 20, 10] [10, 30, 50]
[10, 200, 300, 50]遍历列表有多种方式,各有适用场景:
fruits = ["apple", "banana", "cherry"]
# 方式一:直接遍历元素(最常用)
for fruit in fruits:
print(fruit)
# 方式二:需要索引时用 enumerate
for index, fruit in enumerate(fruits, start=1):
print(f"{index}. {fruit}")
# 方式三:用 range + 索引(需要修改元素时)
for i in range(len(fruits)):
fruits[i] = fruits[i].upper()
print(fruits)apple
banana
cherry
1. apple
2. banana
3. cherry
['APPLE', 'BANANA', 'CHERRY']增删改查方法
列表方法按用途分类,重点关注哪些是「就地修改(in-place)」的:
| 方法 | 作用 | 返回值 | 是否原地修改 |
|---|---|---|---|
append(x) | 末尾追加一个元素 | None | 是 |
extend(iterable) | 末尾追加多个元素 | None | 是 |
insert(i, x) | 在索引 i 处插入 | None | 是 |
remove(x) | 删除第一个值为 x 的元素 | None | 是 |
pop([i]) | 删除并返回索引 i 的元素 | 被删元素 | 是 |
clear() | 清空列表 | None | 是 |
index(x) | 返回首次出现的索引 | int | 否 |
count(x) | 统计出现次数 | int | 否 |
sort() | 排序 | None | 是 |
reverse() | 反转 | None | 是 |
copy() | 浅拷贝 | 新列表 | 否 |
tasks = ["写文档", "写代码", "测试"]
# 添加
tasks.append("部署") # 末尾追加单个
tasks.extend(["监控", "复盘"]) # 末尾追加多个
tasks.insert(0, "需求评审") # 指定位置插入
print(tasks)
# 删除
tasks.remove("测试") # 按值删除
removed = tasks.pop() # 弹出末尾元素
first = tasks.pop(0) # 弹出指定位置
print(f"弹出:{first}, {removed}")
print(tasks)
# 查询
print(tasks.index("写代码"))
print(tasks.count("写代码"))['需求评审', '写文档', '写代码', '测试', '部署', '监控', '复盘']
弹出:需求评审, 复盘
['写文档', '写代码', '部署', '监控']
1
1append 与 extend 的区别
append([1, 2]) 把整个列表当作一个元素加入;extend([1, 2]) 把列表展开后逐个加入。这是初学者最常踩的坑之一。
# append:整个列表作为单个元素
a = [1, 2, 3]
a.append([4, 5])
print("append 后:", a)
# extend:展开后逐个加入
b = [1, 2, 3]
b.extend([4, 5])
print("extend 后:", b)append 后: [1, 2, 3, [4, 5]]
extend 后: [1, 2, 3, 4, 5]排序与反转
sort() 就地排序,sorted() 返回新列表。二者都支持 key 与 reverse 参数:
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sort:就地排序,返回 None
result = numbers.sort()
print(f"返回值:{result},排序后:{numbers}")
# sorted:返回新列表,原列表不变
original = [3, 1, 4, 1, 5]
print(f"原列表:{original},新列表:{sorted(original)}")
print(f"降序:{sorted(original, reverse=True)}")返回值:None,排序后:[1, 1, 2, 3, 4, 5, 6, 9]
原列表:[3, 1, 4, 1, 5],新列表:[1, 1, 3, 4, 5]
降序:[5, 4, 3, 1, 1]按自定义规则排序使用 key 参数:
# 按字符串长度排序
words = ["banana", "apple", "kiwi", "cherry"]
print(sorted(words, key=len))
# 按字典的某个字段排序
users = [
{"name": "alice", "age": 30},
{"name": "bob", "age": 25},
{"name": "cathy", "age": 28},
]
by_age = sorted(users, key=lambda u: u["age"])
for u in by_age:
print(f"{u['name']:<8} {u['age']}")
# 多级排序:先按年龄升序,年龄相同按姓名
users.sort(key=lambda u: (u["age"], u["name"]))
print([u["name"] for u in users])['kiwi', 'apple', 'banana', 'cherry']
bob 25
cathy 28
alice 30
['bob', 'cathy', 'alice']深入理解可变性与引用
列表赋值是引用拷贝,这是最容易引发 Bug 的地方。浅拷贝只复制外层,嵌套对象仍被共享,此时需要深拷贝(Deep Copy):
import copy
# 赋值是引用拷贝:两个变量指向同一个列表
a = [1, 2, 3]
b = a
b.append(4)
print(f"a = {a},是同一对象:{a is b}") # a 也被改变了!
# 浅拷贝:创建新列表,但嵌套元素仍共享
c = [1, 2, 3]
d = c.copy() # 或 c[:] 或 list(c)
d.append(4)
print(f"c = {c},d = {d}")
# 浅拷贝对嵌套结构有隐患
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0][0] = 99
print("浅拷贝后 original:", original) # 被连带修改
# 深拷贝:完全独立的副本
original2 = [[1, 2], [3, 4]]
deep = copy.deepcopy(original2)
deep[0][0] = 99
print("深拷贝后 original2:", original2) # 不受影响a = [1, 2, 3, 4],是同一对象:True
c = [1, 2, 3],d = [1, 2, 3, 4]
浅拷贝后 original: [[99, 2], [3, 4]]
深拷贝后 original2: [[1, 2], [3, 4]]列表推导式
推导式(Comprehension)是 Python 的标志性特性,用一行表达「过滤 + 变换」:
# 基础:生成平方数
squares = [n ** 2 for n in range(1, 6)]
print(squares)
# 带条件过滤
evens = [n for n in range(20) if n % 2 == 0]
print(evens)
# 条件表达式(三元)
labels = ["偶数" if n % 2 == 0 else "奇数" for n in range(6)]
print(labels)
# 嵌套循环:笛卡尔积
pairs = [(x, y) for x in range(2) for y in range(2)]
print(pairs)
# 处理字符串列表
words = ["Hello", "WORLD", "Python"]
normalized = [w.lower() for w in words]
print(normalized)
# 提取嵌套结构
matrix = [[1, 2, 3], [4, 5, 6]]
flattened = [n for row in matrix for n in row]
print(flattened)[1, 4, 9, 16, 25]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
['偶数', '奇数', '偶数', '奇数', '偶数', '奇数']
[(0, 0), (0, 1), (1, 0), (1, 1)]
['hello', 'world', 'python']
[1, 2, 3, 4, 5, 6]推导式家族
推导式不限于列表:{k: v for ...} 生成字典,{x for ...} 生成集合,生成器表达式用 (x for ...)(惰性求值,只遍历一次,适合处理大数据)。
# 字典推导式与集合推导式
print({n: n ** 2 for n in range(1, 6)})
print({len(w) for w in ["apple", "kiwi", "pear", "fig"]}) # 集合自动去重
# 生成器表达式:不一次性构建列表,节省内存
total = sum(n ** 2 for n in range(1000))
print(f"0 到 999 的平方和:{total}"){1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{3, 4, 5}
0 到 999 的平方和:332833500实战示例
示例一:待办事项管理器
综合运用增删改查与排序,实现一个小型任务管理工具:
from dataclasses import dataclass, field
@dataclass
class Task:
"""待办任务"""
title: str
priority: int = 3 # 1 最高,5 最低
done: bool = False
class TaskManager:
"""待办管理器:支持增删、完成、按优先级排序"""
def __init__(self) -> None:
self.tasks: list[Task] = []
def add(self, title: str, priority: int = 3) -> None:
"""添加任务"""
self.tasks.append(Task(title, priority))
def complete(self, title: str) -> bool:
"""按标题标记完成,返回是否成功"""
for task in self.tasks:
if task.title == title:
task.done = True
return True
return False
def remove_done(self) -> int:
"""移除所有已完成任务,返回移除数量"""
before = len(self.tasks)
# 用切片赋值实现原地过滤,保留列表对象引用
self.tasks[:] = [t for t in self.tasks if not t.done]
return before - len(self.tasks)
def pending_sorted(self) -> list[Task]:
"""返回未完成任务,按优先级排序"""
return sorted(
(t for t in self.tasks if not t.done),
key=lambda t: t.priority,
)
manager = TaskManager()
manager.add("修复线上 Bug", priority=1)
manager.add("写周报", priority=4)
manager.add("代码评审", priority=2)
manager.complete("写周报")
removed = manager.remove_done()
print(f"已清理 {removed} 个完成任务")
print("待办(按优先级):")
for task in manager.pending_sorted():
print(f" [P{task.priority}] {task.title}")已清理 1 个完成任务
待办(按优先级):
[P1] 修复线上 Bug
[P2] 代码评审示例二:数据分组与统计
列表配合字典完成分组聚合,是数据处理的常见模式:
from collections import defaultdict
import statistics
# 原始记录:员工、部门、薪资
records: list[tuple[str, str, int]] = [
("alice", "研发", 25000),
("bob", "研发", 22000),
("cathy", "设计", 18000),
("david", "设计", 20000),
("eve", "市场", 16000),
("frank", "市场", 17000),
("grace", "研发", 28000),
]
# 按部门分组
by_dept: dict[str, list[int]] = defaultdict(list)
for _, dept, salary in records:
by_dept[dept].append(salary)
# 统计每个部门的薪资情况
print(f"{'部门':<8}{'人数':>6}{'平均':>10}{'最高':>10}{'最低':>10}")
for dept, salaries in sorted(by_dept.items()):
print(
f"{dept:<8}{len(salaries):>6}"
f"{statistics.mean(salaries):>10.0f}"
f"{max(salaries):>10}"
f"{min(salaries):>10}"
)
# 找出薪资最高的前两位
top2 = sorted(records, key=lambda r: r[2], reverse=True)[:2]
print("\n薪资 Top2:")
for name, dept, salary in top2:
print(f" {name}({dept}){salary:,} 元")部门 人数 平均 最高 最低
市场 2 16500 17000 16000
研发 3 25000 28000 22000
设计 2 19000 20000 18000
薪资 Top2:
grace(研发)28,000 元
alice(研发)25,000 元注意事项
- 赋值是引用拷贝:
b = a不会复制列表,需用a.copy()、a[:]或list(a)。 - 浅拷贝不复制嵌套对象:嵌套结构需
copy.deepcopy()。 [[0] * 3] * 3是陷阱:所有行共享同一个内层列表,应使用推导式。append与extend语义不同:一个加单个元素,一个展开追加。sort()返回None:x = list.sort()会让x变成None,这是常见错误。- 遍历时删除元素会跳过元素:应遍历副本或倒序删除,或用切片赋值过滤。
in判断是线性查找:大列表频繁判断存在性时改用set。- 列表可哈希元素才能放入
set:列表本身不可哈希,不能作为集合元素。
# 错误!遍历时删除会跳过元素
items = [1, 2, 3, 4, 5, 6]
for item in items:
if item % 2 == 0:
items.remove(item)
print(f"错误结果:{items}") # 6 没被删掉
# 正确:遍历副本,或用切片赋值过滤
items2 = [1, 2, 3, 4, 5, 6]
items2[:] = [item for item in items2 if item % 2 != 0]
print(f"正确结果:{items2}")错误结果: [1, 3, 5, 6]
正确结果: [1, 3, 5]最佳实践
- 优先使用列表推导式:比
for+append更简洁且更快。 - 需要索引时用
enumerate:不要用range(len(x))再取元素(除非要修改)。 - 用
zip并行遍历多个列表:for a, b in zip(xs, ys)。 - 成员判断频繁时改用
set:in在列表是 O(n),在集合是 O(1)。 - 用
sorted()避免副作用:不需要改动原列表时优先用sorted。 - 明确区分拷贝语义:浅拷贝用
copy(),深拷贝用deepcopy()。 - 为列表元素类型加注解:
list[Task]比裸list更能表达意图。
下一节
继续学习:条件判断与循环