告别单个变量,用列表和字典批量管理你的 Python 数据

从"单兵作战"到"集团军管理"

如果你已经掌握了变量(存储单个数据)以及 if 判断和 for 循环(控制程序逻辑),那么恭喜你,你已经具备了编写脚本的基础骨架。但此时你可能会遇到一个瓶颈:如果你的程序需要处理成百上千个学生的成绩,或者管理一个拥有几百名员工的通讯录,难道要定义几百个像 student_1student_2 这样的变量吗?显然,这不仅不现实,而且会让代码变得无法维护。

这时候,我们需要引入 Python 中最强大的两个"数据容器":列表 (List)和字典(Dictionary)。

如果把变量比作一个个独立的"小盒子",那么列表就是一个"有序排队的长货架",而字典则是一个"凭钥匙快速查找的智能储物柜"。学会使用它们,标志着你的编程思维从处理"单个数据"跨越到了管理"批量数据",这也是从写几十行的玩具脚本,进化到能处理真实业务数据的关键一步。

列表:有序的万能收纳箱

列表是 Python 中最常用的数据结构之一。想象一下你去银行办事,所有人都在一个队列里按顺序排队,每个人都有一个号码牌(索引)。列表就是这样:它是一个有序的集合,里面的元素可以是任何类型(数字、字符串,甚至另一个列表),并且可以随时修改。

创建与访问:给数据排好队

在 Python 中,列表使用方括号 [] 来定义,元素之间用逗号分隔。

python 复制代码
# 创建一个包含学生姓名的列表
students = ["Alice", "Bob", "Charlie", "David"]

# 访问元素:通过索引(Index)
# 注意:Python 的索引从 0 开始!
print(students[0])  # 输出:Alice (第一个人)
print(students[2])  # 输出:Charlie (第三个人)

# 负数索引:从后往前数
print(students[-1]) # 输出:David (最后一个人)
print(students[-2]) # 输出:Charlie (倒数第二个人)

很多新手容易犯的错误是访问 students[4],这会报错 IndexError,因为只有 4 个人,最大索引是 3。记住:长度是 4,最大索引是 3

核心操作:增删改查

在实际开发中,我们很少静态地定义列表,更多时候是动态地往里面添加或移除数据。以下是最高频的四个操作:

  1. 增加(Append/Insert):

    • append():把新元素加到列表末尾(最常用)。
    • insert():在指定位置插入元素。
  2. 修改(Update):直接通过索引赋值。

  3. 删除(Remove/Pop):

    • remove():根据删除(如果有重复,只删第一个)。
    • pop():根据索引删除,并返回被删掉的值(默认删最后一个)。
  4. 查询(In):判断某个元素是否在列表中。

python 复制代码
fruits = ["苹果", "香蕉"]

# 1. 增加
fruits.append("橙子")        # ['苹果', '香蕉', '橙子']
fruits.insert(1, "葡萄")     # ['苹果', '葡萄', '香蕉', '橙子'] (插在索引 1 的位置)

# 2. 修改
fruits[0] = "红苹果"         # ['红苹果', '葡萄', '香蕉', '橙子']

# 3. 删除
fruits.remove("葡萄")        # 移除值为"葡萄"的元素
last_fruit = fruits.pop()    # 移除最后一个 ("橙子"),并赋值给 last_fruit

# 4. 查询
if "香蕉" in fruits:
    print("还有香蕉可以吃!")

结合循环:遍历列表的威力

高级操作:切片与排序

掌握了列表的基本增删改查后,让我们来看看两个更高级但非常实用的操作:切片排序。这两个功能能让你更灵活地处理列表数据。

列表切片:精准截取子集

切片(Slicing)就像切蛋糕一样,可以从列表中截取一部分。语法是 列表[开始索引:结束索引:步长],其中:

  • 开始索引:包含(从0开始)
  • 结束索引:不包含(到结束索引-1)
  • 步长:每隔几个元素取一个(默认为1)
python 复制代码
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 基础切片
print(numbers[2:5])      # [2, 3, 4]      # 索引2到4(不包含5)
print(numbers[:3])       # [0, 1, 2]      # 从头到索引2
print(numbers[5:])       # [5, 6, 7, 8, 9] # 从索引5到末尾
print(numbers[:])        # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 复制整个列表

# 负数索引切片
print(numbers[-3:])      # [7, 8, 9]      # 最后3个元素
print(numbers[:-2])      # [0, 1, 2, 3, 4, 5, 6, 7] # 除了最后2个

# 带步长的切片
print(numbers[::2])      # [0, 2, 4, 6, 8] # 每隔一个取一个
print(numbers[1::2])     # [1, 3, 5, 7, 9] # 从索引1开始,每隔一个取一个
print(numbers[::-1])     # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] # 反转列表

# 更复杂的切片
print(numbers[1:8:2])    # [1, 3, 5, 7]   # 索引1到7,每隔一个取一个

切片的应用场景:

  1. 获取前N个/后N个元素fruits[:3] 获取前3个水果
  2. 跳过首尾元素data[1:-1] 去掉第一个和最后一个
  3. 反转列表reversed_list = original[::-1]
  4. 提取偶数/奇数位置元素even = numbers[::2], odd = numbers[1::2]
列表排序:让数据井然有序

Python 提供了两种主要的排序方式:sorted() 函数和 .sort() 方法。它们看起来很相似,但有重要区别:

python 复制代码
# 原始数据
scores = [88, 92, 78, 95, 85, 90]
names = ["张三", "李四", "王五", "赵六"]

# 方法1:sorted() 函数 - 返回新列表,不修改原列表
sorted_scores = sorted(scores)  # 升序排序
print("sorted() 返回的新列表:", sorted_scores)  # [78, 85, 88, 90, 92, 95]
print("原列表未被修改:", scores)               # [88, 92, 78, 95, 85, 90]

# 方法2:.sort() 方法 - 原地修改原列表
scores.sort()  # 升序排序
print(".sort() 修改后的原列表:", scores)       # [78, 85, 88, 90, 92, 95]

# 降序排序
scores = [88, 92, 78, 95, 85, 90]
sorted_desc = sorted(scores, reverse=True)  # 降序
print("降序排序:", sorted_desc)              # [95, 92, 90, 88, 85, 78]

scores.sort(reverse=True)  # 原地降序排序
print("原地降序:", scores)                    # [95, 92, 90, 88, 85, 78]

sorted() 与 .sort() 的核心区别:

特性 sorted() 函数 .sort() 方法
返回值 返回新列表,原列表不变 返回 None原地修改原列表
原列表 保持不变 被直接修改
使用方式 新列表 = sorted(原列表) 原列表.sort()
适用场景 需要保留原列表,或对不可变序列排序 直接修改原列表,节省内存
高级排序技巧

1. 按字符串长度排序

python 复制代码
fruits = ["apple", "banana", "cherry", "date", "elderberry"]

# 按长度升序排序
sorted_by_length = sorted(fruits, key=len)
print("按长度排序:", sorted_by_length)  # ['date', 'apple', 'banana', 'cherry', 'elderberry']

# 原地按长度降序排序
fruits.sort(key=len, reverse=True)
print("原地按长度降序:", fruits)  # ['elderberry', 'banana', 'cherry', 'apple', 'date']

2. 按自定义规则排序

python 复制代码
students = [
    {"name": "张三", "score": 85, "age": 18},
    {"name": "李四", "score": 92, "age": 19},
    {"name": "王五", "score": 78, "age": 17},
    {"name": "赵六", "score": 92, "age": 18}
]

# 按分数降序,分数相同时按年龄升序
sorted_students = sorted(students, key=lambda x: (-x["score"], x["age"]))
for student in sorted_students:
    print(f"{student['name']}: 分数{student['score']}, 年龄{student['age']}")
# 李四: 分数92, 年龄19
# 赵六: 分数92, 年龄18
# 张三: 分数85, 年龄18
# 王五: 分数78, 年龄17

3. 切片与排序结合使用

python 复制代码
# 获取分数最高的3名学生
top_3 = sorted(students, key=lambda x: x["score"], reverse=True)[:3]
print("前三名:", [s["name"] for s in top_3])  # ['李四', '赵六', '张三']

# 获取中间50%的数据(去掉前25%和后25%)
data = list(range(100))  # 0-99
n = len(data)
middle = data[n//4 : -n//4]  # 索引25到74
print("中间50%的数据:", middle[:5], "...", middle[-5:])  # [25, 26, 27, 28, 29] ... [70, 71, 72, 73, 74]
实用小贴士
  1. 切片不会修改原列表:切片操作总是返回新列表,原列表保持不变。
  2. 负步长实现反转[::-1] 是最简洁的反转列表方法。
  3. 排序稳定性:Python 的排序是稳定的,相等元素的相对顺序保持不变。
  4. 性能考虑 :对于大数据集,sorted() 需要额外内存,.sort() 更节省内存但会修改原数据。
  5. 复杂排序 :使用 key 参数可以指定排序规则,lambda 函数非常方便。
python 复制代码
# 综合示例:处理学生成绩
grades = [85, 92, 78, 95, 88, 90, 76, 85]

# 1. 获取前3名成绩
top_3 = sorted(grades, reverse=True)[:3]
print(f"前三名成绩: {top_3}")  # [95, 92, 90]

# 2. 获取去掉最高分和最低分后的平均分
trimmed = sorted(grades)[1:-1]  # 去掉最低和最高
average = sum(trimmed) / len(trimmed)
print(f"去掉极端值后的平均分: {average:.1f}")  # 86.7

# 3. 每隔一个学生取一个成绩(模拟分组)
every_other = grades[::2]
print(f"奇数位置学生成绩: {every_other}")  # [85, 78, 88, 76]

掌握了切片和排序,你就能更高效地处理列表数据。切片让你能精准提取需要的部分,而排序则让数据变得井然有序。在实际项目中,这两个操作经常结合使用,比如先排序再取前N个,或者先切片再对子集排序。

常见错误与排查

新手在使用列表时,经常会遇到以下几个典型错误:

1. 索引越界(IndexError)

错误示例:

python 复制代码
fruits = ["苹果", "香蕉", "橙子"]
print(fruits[3])  # 报错:IndexError: list index out of range

问题分析: 列表索引从 0 开始,长度为 3 的列表有效索引是 0、1、2。访问 fruits[3] 试图访问第 4 个元素,但列表只有 3 个元素。

修正方案:

python 复制代码
fruits = ["苹果", "香蕉", "橙子"]
# 方法1:先检查长度
if len(fruits) > 3:
    print(fruits[3])
else:
    print(f"列表只有 {len(fruits)} 个元素,最大索引是 {len(fruits)-1}")

# 方法2:使用安全访问(Python 3.10+)
# print(fruits[3] if len(fruits) > 3 else "索引超出范围")
2. 遍历时修改列表

错误示例:

python 复制代码
numbers = [1, 2, 3, 4, 5]
for num in numbers:
    if num % 2 == 0:  # 如果是偶数
        numbers.remove(num)  # 删除该元素
print(numbers)  # 预期:[1, 3, 5],实际可能是:[1, 3, 4, 5]

问题分析: 在遍历列表的同时修改它(删除或插入元素),会导致迭代器混乱,可能跳过某些元素或产生意外结果。

修正方案:

python 复制代码
numbers = [1, 2, 3, 4, 5]
# 方法1:创建新列表
filtered_numbers = []
for num in numbers:
    if num % 2 != 0:  # 只保留奇数
        filtered_numbers.append(num)
print(filtered_numbers)  # 输出:[1, 3, 5]

# 方法2:使用列表推导式(更简洁)
filtered_numbers = [num for num in numbers if num % 2 != 0]
print(filtered_numbers)  # 输出:[1, 3, 5]

# 方法3:如果需要原地修改,可以遍历副本
numbers = [1, 2, 3, 4, 5]
for num in numbers[:]:  # numbers[:] 创建副本
    if num % 2 == 0:
        numbers.remove(num)
print(numbers)  # 输出:[1, 3, 5]
3. 混淆 append()extend()

错误示例:

python 复制代码
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list1.append(list2)
print(list1)  # 输出:[1, 2, 3, [4, 5, 6]],不是期望的 [1, 2, 3, 4, 5, 6]

问题分析: append() 将整个列表作为一个元素添加,而 extend() 将列表中的每个元素逐个添加。

修正方案:

python 复制代码
list1 = [1, 2, 3]
list2 = [4, 5, 6]

# 正确使用 extend()
list1.extend(list2)
print(list1)  # 输出:[1, 2, 3, 4, 5, 6]

# 或者使用 + 运算符
list1 = [1, 2, 3]
list1 = list1 + list2  # 或 list1 += list2
print(list1)  # 输出:[1, 2, 3, 4, 5, 6]

排查技巧:

  • 遇到 IndexError 时,先用 len() 检查列表长度
  • 需要修改列表时,考虑先创建新列表或遍历副本
  • 不确定方法效果时,在交互式环境(如 Python REPL)中先小规模测试

之前我们学过 for 循环,它和列表简直是天生一对。你不需要关心索引是多少,只需要告诉 Python:"把列表里的每个东西都拿出来做一遍"。

python 复制代码
scores = [85, 92, 78, 90, 65]

# 基础遍历
for score in scores:
    print(f"当前分数:{score}")

# 结合条件判断:筛选出及格分数
print("--- 及格名单 ---")
for score in scores:
    if score >= 60:
        print(f"{score} 分 - 及格")
    else:
        print(f"{score} 分 - 不及格,需要补考")

这种写法不仅代码简洁,而且无论列表里有 5 个数据还是 5 万个数据,代码都不需要改动。这就是批量管理的魅力。

字典:智能的键值对储物柜

如果说列表是靠"排队序号"来找人,那么字典(Dictionary)就是靠"名字标签"来找人。在现实生活中,你想找张三的电话,不会去翻电话簿的第 108 页(索引),而是直接查"张三"这个名字。

字典由 (Key)和 (Value)组成,写成 key: value 的形式,整体用花括号 {} 包裹。

  • (Key):必须是唯一的,且不可变(通常是字符串或数字),相当于储物柜的钥匙或标签。
  • (Value):可以是任何数据类型,相当于柜子里存的东西。

创建与访问:一键直达

python 复制代码
### 完整的字典遍历方法

除了 `.items()`,字典还提供了 `.keys()` 和 `.values()` 方法,分别用于遍历键和值。下面通过一个完整的示例来展示这三种方法的使用场景:

```python
# 创建一个学生信息字典
student_info = {
    "2023001": {"name": "张三", "age": 18, "score": 85},
    "2023002": {"name": "李四", "age": 19, "score": 92},
    "2023003": {"name": "王五", "age": 17, "score": 78},
    "2023004": {"name": "赵六", "age": 20, "score": 88}
}

print("=== 方法1:遍历所有键(.keys())===")
# 适用场景:只需要键,不需要值
for student_id in student_info.keys():
    print(f"学号:{student_id}")

print("\n=== 方法2:遍历所有值(.values())===")
# 适用场景:只需要值,不需要键
for info in student_info.values():
    print(f"姓名:{info['name']},年龄:{info['age']},分数:{info['score']}")

print("\n=== 方法3:同时遍历键和值(.items())===")
# 适用场景:需要同时使用键和值(最常用)
for student_id, info in student_info.items():
    print(f"学号:{student_id},姓名:{info['name']},分数:{info['score']}")

print("\n=== 方法4:直接遍历字典(默认遍历键)===")
# 直接遍历字典等价于遍历 .keys()
for student_id in student_info:
    print(f"学号:{student_id},姓名:{student_info[student_id]['name']}")

输出结果:

复制代码
=== 方法1:遍历所有键(.keys())===
学号:2023001
学号:2023002
学号:2023003
学号:2023004

=== 方法2:遍历所有值(.values())===
姓名:张三,年龄:18,分数:85
姓名:李四,年龄:19,分数:92
姓名:王五,年龄:17,分数:78
姓名:赵六,年龄:20,分数:88

=== 方法3:同时遍历键和值(.items())===
学号:2023001,姓名:张三,分数:85
学号:2023002,姓名:李四,分数:92
学号:2023003,姓名:王五,分数:78
学号:2023004,姓名:赵六,分数:88

=== 方法4:直接遍历字典(默认遍历键)===
学号:2023001,姓名:张三
学号:2023002,姓名:李四
学号:2023003,姓名:王五
学号:2023004,姓名:赵六

三种遍历方法的适用场景

方法 返回值 适用场景 示例
.keys() 所有键的视图 只需要键,不需要值 检查某个键是否存在 获取所有键的列表 for key in my_dict.keys(): if "name" in my_dict.keys(): key_list = list(my_dict.keys())
.values() 所有值的视图 只需要值,不需要键 对值进行统计、计算 提取所有值进行处理 for value in my_dict.values(): total = sum(my_dict.values()) value_list = list(my_dict.values())
.items() (键, 值) 元组的视图 需要同时使用键和值(最常用) 基于键值对进行逻辑判断 需要同时修改键和值 for key, value in my_dict.items(): if value > 60: print(key) new_dict = {k: v*2 for k, v in my_dict.items()}

实际应用示例

场景1:统计学生成绩(使用 .values()

python 复制代码
# 只关心分数,不关心学号
scores = student_info.values()
total_score = sum(info["score"] for info in scores)
average_score = total_score / len(student_info)
print(f"平均分:{average_score:.1f}")

场景2:查找特定条件的学生(使用 .items()

python 复制代码
# 找出分数大于85分的学生
high_scorers = {}
for student_id, info in student_info.items():
    if info["score"] > 85:
        high_scorers[student_id] = info["name"]
print(f"高分学生:{high_scorers}")

场景3:获取所有学号列表(使用 .keys()

python 复制代码
# 需要学号列表进行其他操作
student_ids = list(student_info.keys())
print(f"所有学号:{student_ids}")

性能提示

  • 视图对象.keys().values().items() 返回的是视图对象,不是列表。它们会动态反映字典的变化。
  • 转换为列表 :如果需要真正的列表,可以使用 list() 转换:list(my_dict.keys())
  • 内存效率:视图对象比创建真正的列表更节省内存,特别是对于大型字典。
python 复制代码
# 视图对象会动态更新
my_dict = {"a": 1, "b": 2}
keys_view = my_dict.keys()
print(list(keys_view))  # ['a', 'b']

my_dict["c"] = 3  # 添加新键
print(list(keys_view))  # ['a', 'b', 'c'] 视图自动更新

掌握这三种遍历方法,能让你在处理字典数据时更加得心应手。根据具体需求选择合适的方法,可以让代码更简洁、更高效。# 定义一个表示个人信息的字典

person = {

"name": "李四",

"age": 28,

"city": "北京",

"is_member": True

}

访问值:通过键名,而不是索引

print(person"name") # 输出:李四

print(person"age") # 输出:28

修改值

person"age" = 29 # 李四过生日了

person"city" = "上海" # 李四搬家了

增加新键值对

person"job" = "工程师" # 如果键不存在,会自动创建

删除

del person"is_member" # 删除会员状态

复制代码
**注意**:如果你访问一个不存在的键(如 `person["phone"]`),程序会报错。为了安全起见,推荐使用 `.get()` 方法,它可以在键不存在时返回 `None` 或者你指定的默认值,而不会让程序崩溃。

```python
# 安全访问
phone = person.get("phone", "未登记") 
print(phone) # 输出:未登记

遍历字典:同时获取键和值

常见错误与排查

新手在使用字典时,经常会遇到以下几个典型错误:

1. 键不存在导致 KeyError

错误示例:

python 复制代码
person = {"name": "张三", "age": 25}
print(person["phone"])  # 报错:KeyError: 'phone'

问题分析: 直接使用 dict[key] 访问不存在的键会引发 KeyError 异常,导致程序崩溃。

修正方案:

python 复制代码
person = {"name": "张三", "age": 25}

# 方法1:使用 get() 方法(推荐)
phone = person.get("phone")  # 不存在时返回 None
print(f"电话:{phone if phone else '未登记'}")

# 方法2:get() 方法带默认值
phone = person.get("phone", "未登记")
print(f"电话:{phone}")  # 输出:电话:未登记

# 方法3:使用 in 关键字先检查
if "phone" in person:
    print(f"电话:{person['phone']}")
else:
    print("电话:未登记")

# 方法4:使用 setdefault() 设置默认值(同时会设置到字典)
phone = person.setdefault("phone", "未登记")
print(f"电话:{phone}")  # 输出:电话:未登记,同时字典中会添加 "phone": "未登记"
2. 使用可变类型作为键

错误示例:

python 复制代码
# 尝试用列表作为键(会报错)
key_list = [1, 2, 3]
my_dict = {key_list: "value"}  # 报错:TypeError: unhashable type: 'list'

问题分析: 字典的键必须是不可变类型(immutable),因为字典需要根据键的哈希值来快速查找。列表、字典、集合等可变类型不能作为键。

修正方案:

python 复制代码
# 正确:使用元组(tuple)作为键
coordinates = (10, 20)  # 元组是不可变的
location_dict = {coordinates: "北京"}
print(location_dict[(10, 20)])  # 输出:北京

# 正确:使用字符串、数字、布尔值等不可变类型
student_scores = {
    "张三": 85,      # 字符串 ✓
    1001: "优秀",    # 整数 ✓
    True: "通过",    # 布尔值 ✓
    (1, 2): "坐标"   # 元组 ✓
}

# 错误:尝试用字典作为键
# nested_dict = {{"a": 1}: "value"}  # 报错
3. 遍历时修改字典结构

错误示例:

python 复制代码
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
for name in scores:
    if scores[name] < 80:
        del scores[name]  # 报错:RuntimeError: dictionary changed size during iteration

问题分析: 在遍历字典的同时添加或删除键,会改变字典的大小,导致迭代器失效。

修正方案:

python 复制代码
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}

# 方法1:先收集要删除的键
keys_to_delete = []
for name, score in scores.items():
    if score < 80:
        keys_to_delete.append(name)

for key in keys_to_delete:
    del scores[key]
print(scores)  # 输出:{'Alice': 85, 'Bob': 92}

# 方法2:使用字典推导式创建新字典
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
scores = {name: score for name, score in scores.items() if score >= 80}
print(scores)  # 输出:{'Alice': 85, 'Bob': 92}

# 方法3:遍历字典的键的副本
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
for name in list(scores.keys()):  # list() 创建键的副本
    if scores[name] < 80:
        del scores[name]
print(scores)  # 输出:{'Alice': 85, 'Bob': 92}

排查技巧:

  • 访问不确定是否存在的键时,优先使用 get() 方法
  • 确保字典的键是不可变类型(字符串、数字、元组)
  • 需要修改字典结构时,先收集要修改的内容,遍历完成后再统一处理
  • 使用 print() 输出字典内容,或用 type() 检查键的类型

有时候我们需要同时知道"是谁"以及"他的信息是什么"。这时可以使用 .items() 方法,它会返回一个个 (键,值) 的元组。

python 复制代码
user_scores = {"Alice": 95, "Bob": 88, "Charlie": 72}

# 同时遍历键和值
for name, score in user_scores.items():
    if score > 90:
        level = "优秀"
    elif score > 60:
        level = "合格"
    else:
        level = "待努力"
    
    print(f"学员 {name} 得分 {score},评级:{level}")

这段代码完美融合了第三课学到的 if-elif-else 逻辑和 for 循环,展示了如何用字典管理结构化数据。

实战演练:打造简易通讯录

光看不练假把式。现在,我们将列表、字典、循环和判断结合起来,做一个经典的命令行简易通讯录

需求分析

  1. 需要一个容器存储所有联系人 -> 列表
  2. 每个联系人有多个属性(姓名、电话、邮箱) -> 字典
  3. 程序需要一直运行,直到用户选择退出 -> while 循环
  4. 用户通过输入数字选择功能 -> if-elif 判断

代码实现

运行效果演示

下面是一个模拟运行该通讯录程序的终端交互过程,展示了添加联系人、查看所有联系人和查找联系人的完整流程:

bash 复制代码
=== 简易通讯录管理系统 ===
1. 添加联系人
2. 查看所有联系人
3. 查找联系人
4. 退出系统
========================
请输入功能编号 (1-4): 1

姓名:张三
电话:13800138000
邮箱:zhangsan@example.com
✓ 成功添加联系人:张三

=== 简易通讯录管理系统 ===
1. 添加联系人
2. 查看所有联系人
3. 查找联系人
4. 退出系统
========================
请输入功能编号 (1-4): 1

姓名:李四
电话:13900139000
邮箱:lisi@example.com
✓ 成功添加联系人:李四

=== 简易通讯录管理系统 ===
1. 添加联系人
2. 查看所有联系人
3. 查找联系人
4. 退出系统
========================
请输入功能编号 (1-4): 2

共有 2 位联系人:
1. 姓名:张三, 电话:13800138000, 邮箱:zhangsan@example.com
2. 姓名:李四, 电话:13900139000, 邮箱:lisi@example.com

=== 简易通讯录管理系统 ===
1. 添加联系人
2. 查看所有联系人
3. 查找联系人
4. 退出系统
========================
请输入功能编号 (1-4): 3

请输入要查找的姓名:张三

找到啦!
姓名:张三
电话:13800138000
邮箱:zhangsan@example.com

=== 简易通讯录管理系统 ===
1. 添加联系人
2. 查看所有联系人
3. 查找联系人
4. 退出系统
========================
请输入功能编号 (1-4): 3

请输入要查找的姓名:王五
抱歉,没有找到名为 '王五' 的联系人。

=== 简易通讯录管理系统 ===
1. 添加联系人
2. 查看所有联系人
3. 查找联系人
4. 退出系统
========================
请输入功能编号 (1-4): 4
感谢使用,再见!

交互过程解读:

  1. 添加联系人 :用户输入 1 进入添加模式,依次输入姓名、电话、邮箱,程序会将这些信息打包成字典并添加到 contacts 列表中。
  2. 查看所有联系人 :用户输入 2,程序会先检查列表是否为空,如果不为空则使用 enumerate 遍历所有联系人并格式化输出。
  3. 查找联系人 :用户输入 3 并输入要查找的姓名,程序会遍历列表中的每个字典,比较 name 字段,找到后立即显示详细信息并跳出循环;如果遍历完都没找到,则提示未找到。
  4. 退出系统 :用户输入 4,程序执行 break 跳出 while 循环,结束运行。

通过这个模拟演示,你可以清晰地看到程序如何响应用户输入,以及数据如何在列表和字典之间流动。建议你复制上面的代码到 Python 环境中实际运行,体验完整的交互过程。

python 复制代码
# 初始化一个空列表,用来存放所有联系人字典
contacts = []

def show_menu():
    print("\n=== 简易通讯录管理系统 ===")
    print("1. 添加联系人")
    print("2. 查看所有联系人")
    print("3. 查找联系人")
    print("4. 退出系统")
    print("========================")

while True:
    show_menu()
    choice = input("请输入功能编号 (1-4): ")

    if choice == '1':
        # --- 添加联系人 ---
        name = input("姓名:")
        phone = input("电话:")
        email = input("邮箱:")
        
        # 将单人信息打包成字典
        new_contact = {
            "name": name,
            "phone": phone,
            "email": email
        }
        
        # 将字典添加到列表中
        contacts.append(new_contact)
        print(f"✓ 成功添加联系人:{name}")

    elif choice == '2':
        # --- 查看所有联系人 ---
        if not contacts:
            print("通讯录为空,先去添加几个人吧!")
        else:
            print(f"\n共有 {len(contacts)} 位联系人:")
            # 遍历列表中的每一个字典
            for index, person in enumerate(contacts, 1):
                print(f"{index}. 姓名:{person['name']}, 电话:{person['phone']}, 邮箱:{person['email']}")

    elif choice == '3':
        # --- 查找联系人 ---
        search_name = input("请输入要查找的姓名:")
        found = False
        
        for person in contacts:
            if person["name"] == search_name:
                print(f"\n找到啦!")
                print(f"姓名:{person['name']}")
                print(f"电话:{person['phone']}")
                print(f"邮箱:{person['email']}")
                found = True
                break # 找到后就可以停止循环了
        
        if not found:
            print(f"抱歉,没有找到名为 '{search_name}' 的联系人。")

    elif choice == '4':
        print("感谢使用,再见!")
        break # 跳出 while 循环,结束程序

    else:
        print("输入无效,请重新输入 1-4 的数字。")

代码解析

在这个小项目中,数据结构的设计思路非常清晰:

  • 外层列表 contacts:就像一个文件夹,里面存了很多张"名片"。
  • 内层字典 new_contact:每一张"名片"上记录了具体的字段(姓名、电话等)。
  • 逻辑控制while True 保证了程序不会执行一次就关闭;if/elif 负责分发用户的指令;for 循环负责遍历数据展示或查找。

你可以试着运行这段代码,添加几个朋友的信息,然后尝试查找他们。你会发现,原本零散的变量现在被井井有条地管理起来了。无论你要存 10 个人还是 1000 个人,代码结构完全不需要改变,这就是数据结构带来的扩展性。

课后思考题

光看理论还不够,动手实践才能真正掌握列表和字典的精髓。以下是几个编程练习题,建议你尝试独立完成,遇到问题时可以回顾本文中的「常见错误与排查」部分。

练习题 1:学生成绩管理系统

需求描述:

你需要管理一个班级的学生成绩,每位学生有学号、姓名、语文成绩、数学成绩、英语成绩。

任务要求:

  1. 数据结构设计:思考你会选择列表还是字典来存储所有学生信息?为什么?
  2. 字典设计:如果使用字典,键应该是什么?值应该是什么结构?
  3. 功能实现
    • 实现按学号快速查找某位学生的所有成绩
    • 计算全班语文成绩的平均分
    • 找出数学成绩最高的学生
    • 统计各科目不及格(<60分)的学生人数

示例数据结构参考:

python 复制代码
# 方案1:列表存储字典
students = [
    {"id": "2023001", "name": "张三", "chinese": 85, "math": 92, "english": 78},
    {"id": "2023002", "name": "李四", "chinese": 76, "math": 88, "english": 90},
    # ... 更多学生
]

# 方案2:字典嵌套字典
students_dict = {
    "2023001": {"name": "张三", "chinese": 85, "math": 92, "english": 78},
    "2023002": {"name": "李四", "chinese": 76, "math": 88, "english": 90},
    # ... 更多学生
}

练习题 2:简易购物车系统

需求描述:

设计一个简易的购物车系统,包含商品管理和购物车功能。

任务要求:

  1. 商品管理

    • 商品信息包括:商品ID、名称、单价、库存
    • 使用合适的数据结构存储商品信息
  2. 购物车功能

    • 购物车需要记录:商品ID、购买数量
    • 实现添加商品到购物车
    • 从购物车移除商品
    • 修改商品购买数量
    • 计算购物车总价(考虑库存限制)
  3. 用户交互

    • 显示所有商品列表
    • 显示购物车内容
    • 结算时检查库存是否充足

示例代码框架:

python 复制代码
# 商品数据
products = [
    {"id": "P001", "name": "Python编程书", "price": 59.9, "stock": 50},
    {"id": "P002", "name": "无线鼠标", "price": 129.0, "stock": 30},
    {"id": "P003", "name": "USB-C数据线", "price": 29.9, "stock": 100},
]

# 购物车
cart = {}  # 格式:{"商品ID": 购买数量}

def show_products():
    """显示所有商品"""
    pass

def add_to_cart(product_id, quantity):
    """添加商品到购物车"""
    pass

def calculate_total():
    """计算购物车总价"""
    pass

练习题 3:单词频率统计器

需求描述:

编写一个程序,统计一段文本中每个单词出现的次数。

任务要求:

  1. 输入处理:从用户获取一段文本输入
  2. 文本清洗:去除标点符号,统一转为小写
  3. 单词分割:按空格分割成单词列表
  4. 频率统计:使用字典统计每个单词出现的次数
  5. 结果展示:按出现次数从高到低排序显示

示例输入输出:

复制代码
输入文本:Hello world! Hello Python. Python is great, world is big.

统计结果:
hello: 2
world: 2
python: 2
is: 2
great: 1
big: 1

提示:

  • 使用字符串的 .lower() 方法转为小写
  • 使用字符串的 .replace() 或正则表达式去除标点
  • 使用字典的 .get() 方法进行计数
  • 使用 sorted() 函数对字典按值排序

动手实践建议

  1. 分步实现:不要试图一次性完成所有功能,先实现核心数据结构,再逐步添加功能
  2. 测试驱动:为每个功能编写简单的测试用例,验证代码是否正确
  3. 调试技巧
    • 使用 print() 输出中间结果
    • 使用 type() 检查变量类型
    • 使用 len() 检查数据结构大小
  4. 代码优化:完成基本功能后,思考如何优化代码结构,提高可读性

遇到困难时:

  • 回顾本文中的「常见错误与排查」部分
  • 将大问题拆解成小问题逐个解决
  • 在 Python 交互式环境中测试单个语句的效果

完成这些练习后,你将能够:

  • 根据实际需求选择合适的数据结构
  • 熟练使用列表和字典的组合解决实际问题
  • 理解数据在程序中的流动和处理过程
  • 为后续学习文件操作、函数等更高级概念打下基础

迈向下一步

总结与回顾

通过本课的学习,你已经掌握了 Python 中两个最核心的数据结构:列表和字典。它们是你从处理单个数据迈向批量数据管理的关键工具。让我们最后再回顾一下它们的核心区别与适用场景:

核心区别与适用场景

为了帮助你更直观地理解列表和字典的区别,下表从多个维度进行了对比:

对比维度 列表 (List) 字典 (Dictionary)
数据结构 有序的序列(元素集合) 无序的键值对集合
访问方式 通过**索引(位置)**访问,如 my_list[0] 通过**键(Key)**访问,如 my_dict["name"]
性能特点 - 按索引访问:O(1)(极快) - 按值查找:O(n)(需要遍历) - 插入/删除中间元素:可能较慢 - 按键查找:O(1)(极快) - 内存占用:通常比列表大 - 键必须是不可变类型(字符串、数字、元组)
适用场景 - 有自然顺序的数据(如时间序列、队列) - 需要频繁按位置访问或遍历 - 存储同质化数据(如所有学生姓名) - 有唯一标识符的数据(如学号、身份证号) - 需要快速按键查找 - 存储结构化数据(如学生信息:姓名、年龄、成绩)
常用操作 - append() / insert() 添加 - remove() / pop() 删除 - [index] 修改 - in 查询是否存在 - dict[key] = value 添加/修改 - del dict[key] 删除 - dict.get(key, default) 安全访问 - dict.items() 遍历键值对
核心优势 保持元素顺序,便于按位置操作和遍历 按键快速查找,适合表示映射关系

实际应用选择指南:

详细性能对比表格

为了帮助你更全面地理解列表和字典的性能差异,下表从多个维度进行了详细对比:

对比维度 列表 (List) 字典 (Dictionary) 说明与建议
数据结构 有序序列,元素按插入顺序存储 无序键值对集合,基于哈希表实现 列表保持顺序,字典不保证顺序(Python 3.7+ 保持插入顺序,但不应依赖)
访问方式 通过索引(位置)访问:my_list[0] 通过键(Key)访问:my_dict["name"] 列表适合按位置访问,字典适合按键查找
内存占用 较低,只存储元素本身 较高,需存储键、值、哈希表结构 存储相同数据量时,字典通常比列表多占用 2-3 倍内存
时间复杂度(大O表示法) 时间复杂度表示操作耗时随数据量增长的趋势
- 按索引/键访问 O(1) - 极快 O(1) - 极快 两者都能直接定位,速度极快
- 按值查找 O(n) - 需要遍历整个列表 O(1) - 通过哈希表直接定位 字典的查找优势明显,尤其在大数据量时
- 检查元素是否存在 O(n) - 需要遍历 O(1) - 通过哈希表 字典的 in 操作比列表快得多
- 在末尾添加 O(1) - 极快 O(1) - 极快 两者在末尾添加都很快
- 在开头/中间插入 O(n) - 可能需要移动后续元素 O(1) - 平均情况 列表插入中间元素较慢,字典插入速度稳定
- 删除元素 O(n) - 可能需要移动后续元素 O(1) - 平均情况 列表删除中间元素较慢,字典删除速度快
适用场景 根据主要操作选择数据结构
- 数据特征 有自然顺序、同质化数据 有唯一标识符、结构化数据 列表适合队列、时间序列;字典适合映射关系
- 主要操作 频繁按位置访问、顺序遍历 频繁按键查找、快速检索 列表适合遍历,字典适合查找
- 典型用例 学生名单、日志记录、待办事项 学生信息表、配置参数、缓存数据 根据业务需求选择
常用操作 append(), insert(), remove(), pop(), in dict[key], get(), items(), keys(), values() 掌握核心方法提高编码效率
核心优势 保持元素顺序,便于按位置操作和遍历 按键快速查找,适合表示映射关系 各有侧重,根据需求选择
注意事项 索引从0开始,注意越界;遍历时避免修改 键必须是不可变类型;注意键不存在时的处理 理解常见错误,编写健壮代码

性能选择指南:

  1. 数据量小(< 1000):性能差异不明显,按数据结构需求选择即可
  2. 数据量中等(1000-10万):根据主要操作类型选择
  • 主要操作为遍历、按位置访问 → 选择列表
  • 主要操作为查找、按键访问 → 选择字典
  1. 数据量大(> 10万):需要实测性能,考虑内存限制
  • 内存敏感场景 → 优先考虑列表
  • 查找速度优先 → 优先考虑字典

实际测试示例:

python 复制代码
import time

# 创建测试数据
test_size = 100000
my_list = list(range(test_size))
my_dict = {i: i for i in range(test_size)}

# 测试查找性能
target = test_size - 1  # 查找最后一个元素

# 列表查找
start = time.time()
found = target in my_list
list_time = time.time() - start

# 字典查找
start = time.time()
found = target in my_dict
dict_time = time.time() - start

print(f"数据量: {test_size:,}")
print(f"列表查找耗时: {list_time:.6f} 秒")
print(f"字典查找耗时: {dict_time:.6f} 秒")
print(f"字典比列表快 {list_time/dict_time:.1f} 倍")

关键结论:

  • 列表优势:顺序访问、内存效率、保持元素顺序
  • 字典优势:快速查找、按键访问、灵活的数据结构
  • 组合使用:实际项目中经常组合使用,如列表存储字典,兼顾顺序和查找

理解这些性能特征,能帮助你在不同场景下做出更明智的选择,编写出更高效的程序。

  • 当数据有自然顺序 或需要频繁按位置访问 时,选择列表
  • 当数据有唯一标识符 或需要快速按键查找 时,选择字典
  • 实际项目中经常组合使用:如通讯录案例中,用列表存储多个联系人字典,既保持了顺序又方便按姓名查找。

性能与边界条件

了解列表和字典在不同操作下的性能表现,以及数据量极大时的注意事项,能帮助你编写更高效的程序。

时间复杂度对比

时间复杂度(用大 O 表示法)描述了算法执行时间随数据规模增长的变化趋势。对于列表和字典,常见操作的时间复杂度如下:

操作 列表 (List) 字典 (Dictionary)
按索引/键访问 O(1) - 极快 O(1) - 极快
按值查找 O(n) - 需要遍历整个列表 O(1) - 通过哈希表直接定位
在末尾添加 O(1) - 极快 O(1) - 极快
在开头/中间插入 O(n) - 可能需要移动后续元素 O(1) - 平均情况
删除元素 O(n) - 可能需要移动后续元素 O(1) - 平均情况
检查元素是否存在 O(n) - 需要遍历 O(1) - 通过哈希表

关键理解:

  • 列表的优势在于按索引访问和保持顺序,但查找特定值需要遍历。
  • 字典的优势在于按键查找极快,但需要额外内存维护哈希表结构。
内存占用与数据量考量

当数据量极大时(如百万级以上),除了时间复杂度,还需要关注内存占用:

  1. 字典的内存开销通常更大

    • 字典需要维护哈希表结构,存储键的哈希值、键和值三部分信息
    • 列表只存储元素本身和少量元数据
    • 示例:存储 100 万个整数,字典可能比列表多占用 2-3 倍内存
  2. 使用 sys.getsizeof() 探查内存

    Python 的 sys 模块提供了查看对象内存占用的方法:

    python 复制代码
    import sys
    
    # 比较列表和字典的内存占用
    my_list = [i for i in range(1000)]  # 1000个整数
    my_dict = {i: i for i in range(1000)}  # 1000个键值对
    
    print(f"列表内存占用: {sys.getsizeof(my_list)} 字节")
    print(f"字典内存占用: {sys.getsizeof(my_dict)} 字节")
    
    # 查看单个元素的内存
    print(f"单个列表元素平均: {sys.getsizeof(my_list) / len(my_list):.2f} 字节")
    print(f"单个字典键值对平均: {sys.getsizeof(my_dict) / len(my_dict):.2f} 字节")
  3. 大数据量下的选择建议

    • 优先考虑查找速度:如果需要频繁按键查找,即使字典内存更大也值得使用
    • 内存敏感场景:如果内存有限且主要操作为顺序访问,列表更合适
    • 组合使用:对于超大数据集,可考虑数据库或专门的数据结构库
实际开发中的平衡

在实际项目中,通常遵循以下原则:

  1. 数据量小(< 1000):性能差异不明显,按数据结构需求选择即可
  2. 数据量中等(1000-10万):根据主要操作类型选择,列表适合遍历,字典适合查找
  3. 数据量大(> 10万):需要实测性能,考虑内存限制,必要时使用专业数据结构

简单性能测试示例:

python 复制代码
import time

# 测试列表查找性能
big_list = list(range(1000000))
start = time.time()
999999 in big_list  # 查找最后一个元素
list_time = time.time() - start

# 测试字典查找性能  
big_dict = {i: i for i in range(1000000)}
start = time.time()
999999 in big_dict  # 查找最后一个键
dict_time = time.time() - start

print(f"列表查找耗时: {list_time:.6f} 秒")
print(f"字典查找耗时: {dict_time:.6f} 秒")

记住:没有绝对的好坏,只有适合的场景。理解这些性能特征,能帮助你在不同场景下做出更明智的选择。

课后思考题

为了巩固所学知识,建议你尝试完成以下练习:

  1. 学生成绩管理系统设计

    假设你需要管理一个班级的学生成绩,每位学生有学号、姓名、语文成绩、数学成绩、英语成绩。请思考:

    • 你会选择列表还是字典来存储所有学生信息?为什么?
    • 如果要用字典,键应该是什么?值应该是什么结构?
    • 如何实现按学号快速查找某位学生的所有成绩?
    • 如何计算全班语文成绩的平均分?
  2. 购物车功能模拟

    设计一个简易的购物车系统:

    • 商品信息包括:商品ID、名称、单价、库存
    • 购物车需要记录:商品ID、购买数量
    • 请思考如何用列表和字典组合实现商品展示、加入购物车、计算总价等功能

动手实践建议 :先在白纸或注释中写下你的设计思路,然后用代码实现核心功能。遇到问题时,回顾本文中的「常见错误与排查」部分,使用 print() 调试输出中间结果。


掌握了列表和字典,你已经具备了处理大多数日常数据任务的能力。下一课我们将学习如何将这些内存中的数据持久化保存到文件中,让你的程序真正「记住」数据。准备好了吗?让我们继续前进!

掌握了列表和字典,你就拥有了处理复杂数据的能力。无论是处理 Excel 表格中的数据行(列表套字典),还是解析网络 API 返回的 JSON 格式(本质上就是字典和列表的组合),核心逻辑都是相通的。

不过,细心的你可能发现了:当我们关闭程序再次运行时,刚才辛苦输入的通讯录数据又不见了。这是因为目前所有数据都只保存在内存中。如何让这些数据永久保存下来,哪怕关掉电脑下次打开还在?这就需要引入文件操作了。在下一节课中,我们将学习如何将这些列表和字典写入到文本文件或 CSV 文件中,真正实现数据的持久化存储。

相关推荐
海鸥-w1 小时前
前端学习python第二天手敲笔记整理
前端·python·学习
瑞雪兆丰年兮1 小时前
[从0开始学Java|第十八、十九天]API(常见API&对象克隆&正则表达式)
java·开发语言
KobeSacre1 小时前
JVM G1 垃圾回收器
java·开发语言·jvm
欧神附体1231 小时前
计算机网络之专业名词中英文解释(第一弹)
网络
右耳朵猫AI1 小时前
JavaScript技术周刊 2026年第20周
开发语言·javascript·ecmascript
ylscode1 小时前
Pentest Swarm AI:开源群体智能架构如何重构自主渗透测试的边界
网络·安全·安全威胁分析
MageGojo1 小时前
10 种主题随机诗词:一个 API 解决小程序的诗词内容源
python·小程序·古诗词·api 接入
cooldream20091 小时前
使用 uv 管理 Python 虚拟环境:现代 Python 开发的高效实践
python·uv·mcp
weixin_429630262 小时前
3.51 Centra-Net:一种跨场景的集中式视觉定位网络
网络