Python & AI Tutorials Logo
Python 编程

39. 必备标准库模块

Python 的标准库(standard library)是一组随 Python 一起内置的模块——你不需要额外安装任何东西就能使用它们。这些模块为常见编程任务提供了强大的工具:生成随机数、处理日期与时间、与其他程序交换数据,以及使用超越基础列表(list)和字典(dictionary)的专用数据结构。

在本章中,我们将探索五个在真实世界 Python 编程中你会经常使用的必备标准库模块。

39.1) 使用 random 生成随机性

random 模块提供用于生成随机数并进行随机选择的函数。这对仿真、游戏、测试、数据抽样,以及任何需要不可预测行为的场景都很有用。

39.1.1) 使用 randint() 生成随机整数

randint() 函数会在两个值之间生成一个随机整数,并且两端都是包含(inclusive)的:

python
import random
 
# 模拟掷一个六面骰子
die_roll = random.randint(1, 6)
print(f"You rolled: {die_roll}")  # Output: You rolled: 4 (varies each run)
 
# 生成一个介于 18 到 65 的随机年龄
age = random.randint(18, 65)
print(f"Random age: {age}")  # Output: Random age: 42 (varies)

注意,起始值与结束值都被包含在可能的结果中。randint(1, 6) 可能返回 1、2、3、4、5 或 6——六个值都可能出现。

下面是一个模拟多次掷骰子的实用示例:

python
import random
 
# 模拟掷两个骰子并计算它们的和
die1 = random.randint(1, 6)
die2 = random.randint(1, 6)
total = die1 + die2
 
print(f"Die 1: {die1}")  # Output: Die 1: 3 (varies)
print(f"Die 2: {die2}")  # Output: Die 2: 5 (varies)
print(f"Total: {total}")  # Output: Total: 8 (varies)
 
if total == 7:
    print("Lucky seven!")
elif total == 2 or total == 12:
    print("Snake eyes or boxcars!")

为什么两端都包含:这让 randint() 在常见用例中更直观。当你想要一个从 1 到 6 的数字(比如骰子点数),你写 randint(1, 6),并且 1 和 6 都是可能的结果。

39.1.2) 生成随机浮点数

对于随机小数,使用 random()(返回 0.0 到 1.0 之间的 float)或 uniform()(返回两个指定值之间的 float):

python
import random
 
# 生成一个介于 0.0 与 1.0 之间的随机浮点数(包含 0.0,不包含 1.0)
probability = random.random()
print(f"Random probability: {probability:.4f}")  # Output: Random probability: 0.7284 (varies)
 
# 生成一个介于 15.0 到 30.0 度的随机温度
temperature = random.uniform(15.0, 30.0)
print(f"Temperature: {temperature:.2f}°C")  # Output: Temperature: 23.47°C (varies)
 
# 生成一个介于 $10.00 与 $99.99 的随机价格
price = random.uniform(10.0, 99.99)
print(f"Price: ${price:.2f}")  # Output: Price: $45.67 (varies)

当你需要一个概率值或百分比时,random() 函数很有用。当你需要某个特定范围内的随机小数时,uniform() 函数更合适。

39.1.3) 使用 choice() 进行随机选择

choice() 函数会从一个序列(列表、元组或字符串)中随机选择一个元素:

python
import random
 
# 随机选择一种颜色
colors = ["red", "blue", "green", "yellow", "purple"]
selected_color = random.choice(colors)
print(f"Selected color: {selected_color}")  # Output: Selected color: green (varies)
 
# 从参与者中随机选出一位获胜者
participants = ["Alice", "Bob", "Charlie", "Diana"]
winner = random.choice(participants)
print(f"The winner is: {winner}")  # Output: The winner is: Bob (varies)
 
# 从字符串中随机选择一个字符
vowels = "aeiou"
random_vowel = random.choice(vowels)
print(f"Random vowel: {random_vowel}")  # Output: Random vowel: i (varies)

这对游戏、随机抽样或选择随机测试数据特别有用。序列中的每个元素都有相同的被选中概率。

下面是一个更复杂的示例,用来模拟一个简单的问答游戏:

python
import random
 
# 测验题目与答案
questions = [
    ("What is 2 + 2?", "4"),
    ("What is the capital of France?", "Paris"),
    ("What color is the sky?", "blue")
]
 
# 随机选择一个问题
question, correct_answer = random.choice(questions)
print(f"Question: {question}")
 
user_answer = input("Your answer: ")
if user_answer.lower() == correct_answer.lower():
    print("Correct!")
else:
    print(f"Wrong! The answer was: {correct_answer}")

39.1.4) 使用 sample() 选择多个随机项

当你需要从序列中选择多个不重复的项目时,使用 sample()。这就像从一副牌里抽牌且不放回——一旦某个项被选中,它就不会再次被选中:

python
import random
 
# 为小组项目随机选择 3 名学生
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
group = random.sample(students, 3)
print(f"Group members: {group}")  # Output: Group members: ['Diana', 'Alice', 'Frank'] (varies)
 
# 从 1 到 50 抽取 5 个彩票号码(无重复)
lottery_numbers = random.sample(range(1, 51), 5)
lottery_numbers.sort()  # Sort for display
print(f"Lottery numbers: {lottery_numbers}")  # Output: Lottery numbers: [7, 15, 23, 38, 49] (varies)

sample() 的第二个参数指定要选择多少个项目。这个数量必须小于或等于序列的长度——你不能选择超过可用数量的项目。

39.1.5) 使用 shuffle() 打乱序列

shuffle() 函数会原地(in place)随机重排列表中的元素(修改原始列表):

python
import random
 
# 洗牌一副扑克牌
cards = ["A♠", "K♠", "Q♠", "J♠", "10♠", "9♠", "8♠", "7♠"]
print(f"Original: {cards}")
random.shuffle(cards)
print(f"Shuffled: {cards}")  # Output: Shuffled: ['Q♠', '7♠', 'A♠', '10♠', '9♠', 'J♠', 'K♠', '8♠'] (varies)
 
# 打乱测验题目以实现随机顺序
questions = ["Question 1", "Question 2", "Question 3", "Question 4"]
random.shuffle(questions)
print(f"Randomized order: {questions}")  # Output: Randomized order: ['Question 3', 'Question 1', 'Question 4', 'Question 2'] (varies)

Random 模块函数

randint: 随机整数

random/uniform: 随机浮点数

choice: 选取一个项目

sample: 选取多个不重复项目

shuffle: 原地重排列表

两端都包含

random: 0.0 到 1.0

uniform: 自定义范围

每个元素概率相等

无重复

修改原始列表

39.2) 处理日期与时间

datetime 模块提供用于处理日期、时间以及时间间隔的类。这对日程安排、日志记录、计算持续时间,以及任何需要跟踪事件发生时间的应用都至关重要。

39.2.1) 获取当前日期与时间

datetime 类表示一个特定的时间点,同时包含日期与时间组件:

python
from datetime import datetime
 
# 获取当前日期与时间
now = datetime.now()
print(f"Current datetime: {now}")
# Output: Current datetime: 2026-01-02 14:30:45.123456
 
# 访问各个组件
print(f"Year: {now.year}")      # Output: Year: 2026
print(f"Month: {now.month}")    # Output: Month: 1
print(f"Day: {now.day}")        # Output: Day: 2
print(f"Hour: {now.hour}")      # Output: Hour: 14
print(f"Minute: {now.minute}")  # Output: Minute: 30
print(f"Second: {now.second}")  # Output: Second: 45

如果只需要日期(不包含时间),使用 date 类:

python
from datetime import date
 
# 获取今天的日期
today = date.today()
print(f"Today: {today}")  # Output: Today: 2026-01-02
 
print(f"Year: {today.year}")    # Output: Year: 2026
print(f"Month: {today.month}")  # Output: Month: 1
print(f"Day: {today.day}")      # Output: Day: 2

39.2.2) 创建特定日期与时间

你可以为特定时间点创建 datetimedate 对象:

python
from datetime import datetime, date
 
# 创建一个特定日期
birthday = date(1995, 7, 15)
print(f"Birthday: {birthday}")  # Output: Birthday: 1995-07-15
 
# 创建一个特定 datetime
meeting = datetime(2026, 3, 15, 14, 30)  # March 15, 2026 at 2:30 PM
print(f"Meeting: {meeting}")  # Output: Meeting: 2026-03-15 14:30:00

这对于表示截止日期、预约、历史日期,或任何固定时间点都很有用:

python
from datetime import date
 
# 项目中的重要日期
project_start = date(2026, 1, 15)
project_end = date(2026, 6, 30)
 
print(f"Project duration: {project_start} to {project_end}")
# Output: Project duration: 2026-01-15 to 2026-06-30

39.2.3) 使用 timedelta 计算时间差

timedelta 类表示一个持续时间——两个日期或时间之间的差值。你可以用它来计算已经过去了多少时间,或对日期进行加/减时间:

python
from datetime import date, timedelta
 
# 计算年龄
birth_date = date(1995, 7, 15)
today = date(2026, 1, 2)
age_delta = today - birth_date
 
print(f"Days since birth: {age_delta.days}")  # Output: Days since birth: 11128
print(f"Years (approximate): {age_delta.days // 365}")  # Output: Years (approximate): 30

当你用一个日期减去另一个日期时,会得到一个 timedelta 对象。days 属性会告诉你该持续时间包含的天数。

你也可以直接创建 timedelta 对象来表示特定持续时间:

python
from datetime import date, timedelta
 
# 给日期加上天数
today = date(2026, 1, 2)
one_week = timedelta(days=7)
next_week = today + one_week
 
print(f"Today: {today}")        # Output: Today: 2026-01-02
print(f"Next week: {next_week}")  # Output: Next week: 2026-01-09
 
# 从日期中减去天数
thirty_days_ago = today - timedelta(days=30)
print(f"30 days ago: {thirty_days_ago}")  # Output: 30 days ago: 2025-12-03

timedelta 可以表示天、秒、微秒、毫秒、分钟、小时和周:

python
from datetime import datetime, timedelta
 
# 计算一个截止时间
now = datetime(2026, 1, 2, 14, 30)
deadline = now + timedelta(hours=48, minutes=30)
 
print(f"Current time: {now}")    # Output: Current time: 2026-01-02 14:30:00
print(f"Deadline: {deadline}")   # Output: Deadline: 2026-01-04 15:00:00
 
# 计算剩余时间
time_left = deadline - now
print(f"Hours remaining: {time_left.total_seconds() / 3600}")  # Output: Hours remaining: 48.5

total_seconds() 方法会把整个持续时间转换为秒,然后你可以再把它换算成小时、分钟或任何其他单位。

下面是一个计算项目里程碑的实用示例:

python
from datetime import date, timedelta
 
# 项目规划
project_start = date(2026, 1, 15)
sprint_duration = timedelta(weeks=2)
 
sprint_1_end = project_start + sprint_duration
sprint_2_end = sprint_1_end + sprint_duration
sprint_3_end = sprint_2_end + sprint_duration
 
print(f"Sprint 1: {project_start} to {sprint_1_end}")
# Output: Sprint 1: 2026-01-15 to 2026-01-29
print(f"Sprint 2: {sprint_1_end} to {sprint_2_end}")
# Output: Sprint 2: 2026-01-29 to 2026-02-12
print(f"Sprint 3: {sprint_2_end} to {sprint_3_end}")
# Output: Sprint 3: 2026-02-12 to 2026-02-26

39.2.4) 比较日期与时间

date 与 datetime 对象可以使用标准比较运算符进行比较:

python
from datetime import date
 
# 比较日期
date1 = date(2026, 1, 15)
date2 = date(2026, 2, 20)
date3 = date(2026, 1, 15)
 
print(date1 < date2)   # Output: True
print(date1 == date3)  # Output: True
print(date2 > date1)   # Output: True

这对于检查截止日期、验证日期范围以及对日期进行排序都很有用:

python
from datetime import date
 
# 检查某个日期是否在过去
event_date = date(2025, 12, 25)
today = date(2026, 1, 2)
 
if event_date < today:
    print("This event has already passed")  # Output: This event has already passed
else:
    print("This event is upcoming")
 
# 对日期列表排序
important_dates = [
    date(2026, 3, 15),
    date(2026, 1, 10),
    date(2026, 2, 28)
]
 
important_dates.sort()
print("Dates in order:")  # Output: Dates in order:
for d in important_dates:
    print(f"  {d}")
# Output:
#   2026-01-10
#   2026-02-28
#   2026-03-15

39.2.5) 使用 strftime() 格式化日期与时间

strftime() 方法(string format time)会把日期与时间转换为格式化的字符串。你用特殊代码来指定格式:

python
from datetime import datetime
 
now = datetime(2026, 1, 2, 14, 30, 45)
 
# 常见日期格式
print(now.strftime("%Y-%m-%d"))           # Output: 2026-01-02
print(now.strftime("%m/%d/%Y"))           # Output: 01/02/2026
print(now.strftime("%B %d, %Y"))          # Output: January 02, 2026
print(now.strftime("%A, %B %d, %Y"))      # Output: Friday, January 02, 2026
 
# 常见时间格式
print(now.strftime("%H:%M:%S"))           # Output: 14:30:45
print(now.strftime("%I:%M %p"))           # Output: 02:30 PM
 
# 组合格式
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # Output: 2026-01-02 14:30:45
print(now.strftime("%B %d, %Y at %I:%M %p"))  # Output: January 02, 2026 at 02:30 PM

常见格式代码:

Code描述示例
%Y带世纪的年份2026
%m以零填充的月份数字 (01-12)01
%d以零填充的日期数字 (01-31)02
%B月份全称January
%b月份简称Jan
%A星期全称Friday
%a星期简称Fri
%H24 小时制小时 (00-23)14
%I12 小时制小时 (01-12)02
%M分钟 (00-59)30
%S秒 (00-59)45
%pAM/PMPM

下面是一个创建日志条目的实用示例:

python
from datetime import datetime
 
def log_event(message):
    """记录带时间戳的事件"""
    now = datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    print(f"[{timestamp}] {message}")
 
log_event("User logged in")
# Output: [2026-01-02 14:30:45] User logged in
 
log_event("File uploaded successfully")
# Output: [2026-01-02 14:30:45] File uploaded successfully

39.2.6) 使用 strptime() 从字符串解析日期

strptime() 函数(string parse time)会把格式化字符串转换回 datetime 对象。你指定同样的格式代码来告诉 Python 如何解释这个字符串:

python
from datetime import datetime
 
# 解析不同的日期格式
date_str1 = "2026-01-15"
date1 = datetime.strptime(date_str1, "%Y-%m-%d")
print(f"Parsed: {date1}")  # Output: Parsed: 2026-01-15 00:00:00
 
date_str2 = "January 15, 2026"
date2 = datetime.strptime(date_str2, "%B %d, %Y")
print(f"Parsed: {date2}")  # Output: Parsed: 2026-01-15 00:00:00
 
# 解析带时间的 datetime
datetime_str = "2026-01-15 14:30:00"
dt = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
print(f"Parsed: {dt}")  # Output: Parsed: 2026-01-15 14:30:00

当你从文件、用户输入或外部数据源读取日期时,这一点至关重要:

python
from datetime import datetime
 
# 解析用户输入
user_input = "03/15/2026"
try:
    event_date = datetime.strptime(user_input, "%m/%d/%Y")
    print(f"Event scheduled for: {event_date.strftime('%B %d, %Y')}")
    # Output: Event scheduled for: March 15, 2026
except ValueError:
    print("Invalid date format. Please use MM/DD/YYYY")

重要:格式字符串必须与输入字符串完全匹配,否则你会得到 ValueError

python
from datetime import datetime
 
# 这会失败 - 格式不匹配
try:
    datetime.strptime("2026-01-15", "%m/%d/%Y")  # Wrong format
except ValueError as e:
    print(f"Error: {e}")
    # Output: Error: time data '2026-01-15' does not match format '%m/%d/%Y'

datetime 模块

datetime.now: 当前日期/时间

date.today: 当前日期

datetime/date: 创建特定日期

timedelta: 时间持续量

strftime: 格式化为字符串

strptime: 从字符串解析

对日期加/减

计算差值

%Y, %m, %d, %H, %M, %S

必须与格式完全匹配

39.3) 读取与写入 JSON 数据

JSON(JavaScript Object Notation)是一种用于存储与交换结构化数据的文本格式。它是 Web API、配置文件以及程序间数据交换中最常见的格式。Python 的 json 模块让 Python 数据结构与 JSON 文本之间的转换变得很容易。

39.3.1) 理解 JSON 结构

JSON 看起来类似 Python 字典和列表,但也有一些不同:

JSON 支持这些数据类型:

  • 对象(类似 Python 字典):{"name": "Alice", "age": 30}
  • 数组(类似 Python 列表):[1, 2, 3, 4]
  • 字符串:"hello"(必须使用双引号)
  • 数字:423.14
  • 布尔值:truefalse(小写)
  • 空值:null(类似 Python 的 None

与 Python 的关键差异:

  • JSON 使用 true/false/null,而不是 Python 的 True/False/None
  • JSON 字符串必须使用双引号("text"),不能用单引号
  • JSON 不直接支持元组、集合或自定义对象

下面是 JSON 数据的样子:

json
{
    "name": "Alice Johnson",
    "age": 30,
    "email": "alice@example.com",
    "is_active": true,
    "scores": [85, 92, 78, 95],
    "address": {
        "street": "123 Main St",
        "city": "Springfield",
        "zip": "12345"
    }
}

注意: 这是一段纯 JSON 文本,不是 Python 代码。注意小写的 true 以及双引号的使用。

39.3.2) 使用 dumps() 将 Python 数据转换为 JSON

dumps() 函数(dump string)会把 Python 数据结构转换为 JSON 格式的字符串:

python
import json
 
student = {
    "name": "Alice Johnson",
    "age": 30,
    "email": "alice@example.com",
    "is_active": True,
    "scores": [85, 92, 78, 95]
}
 
# 将字典转换为 JSON
json_string = json.dumps(student)
print(json_string)
# Output: {"name": "Alice Johnson", "age": 30, "email": "alice@example.com", "is_active": true, "scores": [85, 92, 78, 95]}
 
print(type(json_string))  # Output: <class 'str'>

注意输出中 Python 的 True 是如何变成 JSON 的 true 的。dumps() 函数会自动处理这些转换。

如果想要更易读的输出,使用 indent 参数:

python
import json
 
student = {
    "name": "Alice Johnson",
    "age": 30,
    "scores": [85, 92, 78, 95]
}
 
# 使用缩进进行美化输出
json_string = json.dumps(student, indent=2)
print(json_string)
# Output:
# {
#   "name": "Alice Johnson",
#   "age": 30,
#   "scores": [
#     85,
#     92,
#     78,
#     95
#   ]
# }

indent 参数指定每一级缩进使用多少个空格。这会让 JSON 更容易阅读,尤其是面对复杂的嵌套结构时。

39.3.3) 使用 loads() 将 JSON 转换为 Python 数据

loads() 函数(load string)会把 JSON 格式字符串转换回 Python 数据结构:

python
import json
 
# JSON 字符串(比如你可能从 Web API 接收到的)
json_string = '{"name": "Bob Smith", "age": 25, "scores": [90, 88, 92]}'
 
# 转换为 Python 字典
student = json.loads(json_string)
print(student)  # Output: {'name': 'Bob Smith', 'age': 25, 'scores': [90, 88, 92]}
print(type(student))  # Output: <class 'dict'>
 
# 像任何 Python 字典一样访问数据
print(f"Name: {student['name']}")  # Output: Name: Bob Smith
print(f"Average score: {sum(student['scores']) / len(student['scores'])}")
# Output: Average score: 90.0

JSON 的 truefalsenull 会自动转换为 Python 的 TrueFalseNone

python
import json
 
json_string = '{"active": true, "verified": false, "middle_name": null}'
data = json.loads(json_string)
 
print(data)  # Output: {'active': True, 'verified': False, 'middle_name': None}
print(type(data["active"]))  # Output: <class 'bool'>
print(type(data["middle_name"]))  # Output: <class 'NoneType'>

39.3.4) 使用 dump() 将 JSON 写入文件

dump() 函数会把 Python 数据直接以 JSON 格式写入文件:

python
import json
 
# 学生记录
students = [
    {"name": "Alice", "age": 20, "gpa": 3.8},
    {"name": "Bob", "age": 22, "gpa": 3.5},
    {"name": "Charlie", "age": 21, "gpa": 3.9}
]
 
# 写入 JSON 文件
with open("students.json", "w") as file:
    json.dump(students, file, indent=2)
 
print("Data written to students.json")
# Output: Data written to students.json

运行这段代码后,文件 students.json 的内容为:

json
[
  {
    "name": "Alice",
    "age": 20,
    "gpa": 3.8
  },
  {
    "name": "Bob",
    "age": 22,
    "gpa": 3.5
  },
  {
    "name": "Charlie",
    "age": 21,
    "gpa": 3.9
  }
]

为什么用 dump() 而不是 dumps() dump() 会直接写入文件,这比先转换成字符串再写入字符串更高效。处理文件时用 dump(),当你需要把 JSON 作为字符串(例如通过网络发送)时用 dumps()

39.3.5) 使用 load() 从文件读取 JSON

load() 函数从文件读取 JSON 数据并把它转换为 Python 数据结构:

python
import json
 
# 从我们之前创建的 JSON 文件读取
with open("students.json", "r") as file:
    students = json.load(file)
 
print(f"Loaded {len(students)} students")  # Output: Loaded 3 students
 
# 使用数据
for student in students:
    print(f"{student['name']}: GPA {student['gpa']}")
# Output:
# Alice: GPA 3.8
# Bob: GPA 3.5
# Charlie: GPA 3.9

39.3.6) 处理 JSON 错误

处理 JSON 时,你可能会遇到无效数据。务必处理潜在错误:

python
import json
 
# 无效 JSON - 缺少结束引号
invalid_json = '{"name": "Alice", "age": 30'
 
try:
    data = json.loads(invalid_json)
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")
    # Output: Invalid JSON: Expecting ',' delimiter: line 1 column 28 (char 27)

这在从外部来源(文件、Web API、用户输入)读取 JSON 时尤其重要,因为你无法保证数据一定有效:

python
import json
 
def load_config(filename):
    """从 JSON 文件加载配置,并进行错误处理"""
    try:
        with open(filename, "r") as file:
            config = json.load(file)
            return config
    except FileNotFoundError:
        print(f"Config file '{filename}' not found")
        return None
    except json.JSONDecodeError as e:
        print(f"Invalid JSON in '{filename}': {e}")
        return None
 
# 尝试加载配置
config = load_config("config.json")
if config:
    print(f"Configuration loaded: {config}")
else:
    print("Using default configuration")

39.3.7) JSON 实战示例:保存与加载应用状态

下面是一个完整示例,展示如何保存与加载应用数据:

python
import json
 
def save_game_state(filename, player_data):
    """将游戏存档保存到 JSON 文件"""
    with open(filename, "w") as file:
        json.dump(player_data, file, indent=2)
    print(f"Game saved to {filename}")
 
def load_game_state(filename):
    """从 JSON 文件加载游戏存档"""
    try:
        with open(filename, "r") as file:
            player_data = json.load(file)
        print(f"Game loaded from {filename}")
        return player_data
    except FileNotFoundError:
        print("No saved game found")
        return None
 
# 游戏数据
player = {
    "name": "Hero",
    "level": 5,
    "health": 85,
    "inventory": ["sword", "shield", "potion"],
    "position": {"x": 10, "y": 20}
}
 
# 保存游戏
save_game_state("savegame.json", player)
# Output: Game saved to savegame.json
 
# 之后加载游戏
loaded_player = load_game_state("savegame.json")
# Output: Game loaded from savegame.json
 
if loaded_player:
    print(f"Welcome back, {loaded_player['name']}!")
    print(f"Level: {loaded_player['level']}, Health: {loaded_player['health']}")
    # Output:
    # Welcome back, Hero!
    # Level: 5, Health: 85

json 模块

dumps: Python → JSON 字符串

loads: JSON 字符串 → Python

dump: Python → JSON 文件

load: JSON 文件 → Python

indent 参数用于提升可读性

处理类型转换

比 dumps + write 更高效

处理 JSONDecodeError

39.4) collections 中的实用容器

collections 模块提供专用容器类型,用额外功能扩展了 Python 内置容器(列表、字典、集合)。这些容器比使用基础数据结构更优雅地解决常见问题。

39.4.1) 使用 Counter 统计项目数量

Counter 类用于统计可哈希对象。它是字典的子类,将项目作为键,并将其计数作为值。

Counter 可接受的输入:

  • 任意可迭代对象(列表、字符串、元组等)
  • 另一个带计数的字典
  • 带计数的关键字参数

Counter 存储的内容:

  • 一个字典,其中键是项目,值是它们的计数
  • 示例:Counter(['a', 'b', 'a']) 存储 {'a': 2, 'b': 1}

相对普通字典的关键优势:

  • 对缺失键返回 0,而不是抛出 KeyError
  • 提供 most_common() 等计数专用方法
  • 支持计数器之间的算术运算

基本用法

python
from collections import Counter
 
# 统计单词中的字母出现次数
word = "mississippi"
letter_counts = Counter(word)
print(letter_counts)
# Output: Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
 
# 像字典一样访问计数
print(f"Number of 'i's: {letter_counts['i']}")
# Output: Number of 'i's: 4
 
print(f"Number of 'z's: {letter_counts['z']}")
# Output: Number of 'z's: 0 (returns 0 for missing keys, no KeyError!)

从不同来源创建 Counter

python
from collections import Counter
 
# 从列表创建
votes = ["Alice", "Bob", "Alice", "Charlie", "Alice", "Bob", "Alice"]
vote_counts = Counter(votes)
print(vote_counts)
# Output: Counter({'Alice': 4, 'Bob': 2, 'Charlie': 1})
 
# 从字符串创建(统计每个字符)
letter_counts = Counter("hello")
print(letter_counts)
# Output: Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1})
 
# 从字典创建
existing_counts = {'apple': 3, 'banana': 2}
fruit_counts = Counter(existing_counts)
print(fruit_counts)
# Output: Counter({'apple': 3, 'banana': 2})
 
# 从关键字参数创建
color_counts = Counter(red=5, blue=3, green=2)
print(color_counts)
# Output: Counter({'red': 5, 'blue': 3, 'green': 2})

使用 most_common() 查找最常见项目

方法签名: most_common(n=None)

参数:

  • n(可选):要返回的最常见项目数量
  • 如果省略 n 或为 None,则返回所有项目

返回值:

  • 一个由 (item, count) 元组组成的列表
  • 按计数排序,最高的在前
  • 如果计数相同,则按首次出现的顺序排列
python
from collections import Counter
 
# 分析文本中的词频
text = "the quick brown fox jumps over the lazy dog the fox"
words = text.split()
word_counts = Counter(words)
 
# 获取最常见的 3 个单词
top_3 = word_counts.most_common(3)
print(top_3)
# Output: [('the', 3), ('fox', 2), ('quick', 1)]

Counter 的算术运算

你可以对 Counter 对象进行加、减以及其他操作:

python
from collections import Counter
 
# 统计两组中的项目数量
group1 = Counter(["apple", "banana", "apple", "orange"])
print(group1)
# Output: Counter({'apple': 2, 'banana': 1, 'orange': 1})
 
group2 = Counter(["banana", "banana", "grape", "apple"])
print(group2)
# Output: Counter({'banana': 2, 'grape': 1, 'apple': 1})
 
# 将计数相加
combined = group1 + group2
print(combined)
# Output: Counter({'apple': 3, 'banana': 3, 'orange': 1, 'grape': 1})
 
# 相减计数(只保留正数结果)
difference = group1 - group2
print(difference)
# Output: Counter({'apple': 1, 'orange': 1})
# banana: 1 - 2 = -1 (negative, so excluded)
# grape: not in group1, so excluded

实用示例:分析学生成绩

python
from collections import Counter
 
# 成绩分布
grades = ["A", "B", "A", "C", "B", "A", "B", "D", "A", "B", "C", "A"]
grade_counts = Counter(grades)
 
print(f"Total students: {len(grades)}")
# Output: Total students: 12
 
print("\nGrade Distribution:")
for grade, count in grade_counts.most_common():
    percentage = (count / len(grades)) * 100
    bar = "█" * count
    print(f"  {grade}: {count} students ({percentage:4.1f}%) {bar}")
# Output:
# Grade Distribution:
#   A: 5 students (41.7%) █████
#   B: 4 students (33.3%) ████
#   C: 2 students (16.7%) ██
#   D: 1 students ( 8.3%) █

39.4.2) 使用 defaultdict 创建带默认值的字典

defaultdict 类是字典的子类,当你访问缺失键时,它会自动创建带默认值的条目。这消除了在使用前检查键是否存在的需求。

defaultdict 可接受的输入:

  • 一个默认工厂(default factory)函数(必需):一个可调用对象,用于为缺失键返回默认值
  • 普通 dict 可接受的任何参数(键值对、另一个字典、关键字参数)

相对普通字典的关键优势:

  • 使用前无需检查键是否存在
  • 自动为缺失键初始化默认值
  • 在分组、计数与累加操作中,代码更简洁、更易读

理解默认工厂

创建 defaultdict 时,你必须提供一个默认工厂——一个不接收参数、返回默认值的可调用对象(函数)。常见默认工厂包括:

  • int - 返回 0(用于计数)
  • list - 返回 [](用于分组)
  • set - 返回 set()(用于收集唯一项目)
  • str - 返回 ''(用于字符串拼接)
  • lambda: value - 返回自定义默认值
python
from collections import defaultdict
 
# 不同的默认工厂
counts = defaultdict(int)        # 缺失键返回 0
groups = defaultdict(list)       # 缺失键返回 []
unique = defaultdict(set)        # 缺失键返回 set()
custom = defaultdict(lambda: "N/A")  # 缺失键返回 "N/A"
 
# 用缺失键进行测试
print(counts['missing'])     # Output: 0
print(groups['missing'])     # Output: []
print(unique['missing'])     # Output: set()
print(custom['missing'])     # Output: N/A

基本用法:使用 defaultdict 计数

对比普通字典与 defaultdict 的计数写法:

python
from collections import defaultdict
 
word = "mississippi"
 
# 普通字典 - 需要检查键是否存在
regular_dict = {}
for letter in word:
    if letter not in regular_dict:
        regular_dict[letter] = 0
    regular_dict[letter] += 1
 
print(regular_dict)
# Output: {'m': 1, 'i': 4, 's': 4, 'p': 2}
 
# defaultdict - 自动创建带默认值的条目
letter_counts = defaultdict(int)  # int() 返回 0
for letter in word:
    letter_counts[letter] += 1  # 无需检查键是否存在!
 
print(dict(letter_counts))
# Output: {'m': 1, 'i': 4, 's': 4, 'p': 2}

工作原理:

  1. 当你第一次访问新字母的 letter_counts[letter] 时,defaultdict 会调用 int() 并返回 0
  2. 随后会创建该键并赋值为 0,然后 += 1 使其变为 1
  3. 对已存在的键,它与普通字典行为一致

使用 defaultdict(list) 分组

一个常见用例是将项目按类别分组:

python
from collections import defaultdict
 
students = [
    ("Alice", "A"),
    ("Bob", "B"),
    ("Charlie", "A"),
    ("Diana", "C"),
    ("Eve", "B"),
    ("Frank", "A")
]
 
# 按成绩分组学生
# 使用 defaultdict - 干净且简单
students_by_grade = defaultdict(list)
for name, grade in students:
    students_by_grade[grade].append(name)
 
print(dict(students_by_grade))
# Output: {'A': ['Alice', 'Charlie', 'Frank'], 'B': ['Bob', 'Eve'], 'C': ['Diana']}
 
# 访问一个尚不存在的成绩
print(students_by_grade["D"])  # Output: [] (empty list, not KeyError!)

工作原理:

  1. 当你第一次访问新成绩的 students_by_grade[grade] 时,defaultdict 会调用 list() 并返回 []
  2. 随后创建该键并赋值为空列表,然后 .append(name) 添加第一个学生
  3. 对已存在的成绩,它只会向现有列表追加

从现有字典创建 defaultdict

你可以用已有数据初始化一个 defaultdict

python
from collections import defaultdict
 
# 从现有计数开始
existing_data = {'apple': 5, 'banana': 3}
 
# 从现有字典创建 defaultdict
fruit_counts = defaultdict(int, existing_data)
 
# 追加更多计数
fruit_counts['apple'] += 2     # 5 + 2 = 7
fruit_counts['orange'] += 1    # 0 + 1 = 1 (new key, starts at 0)
 
print(dict(fruit_counts))
# Output: {'apple': 7, 'banana': 3, 'orange': 1}

自定义默认工厂

你可以提供任何可调用对象作为默认工厂:

python
from collections import defaultdict
 
# 使用 lambda 自定义默认值
page_views = defaultdict(lambda: {'views': 0, 'unique': 0})
 
page_views['home']['views'] = 100
page_views['home']['unique'] = 75
 
print(page_views['home'])
# Output: {'views': 100, 'unique': 75}
 
print(page_views['about'])  # New key gets default dictionary
# Output: {'views': 0, 'unique': 0}

重要说明

访问 vs. 检查键:

python
from collections import defaultdict
 
counts = defaultdict(int)
 
# 访问缺失键会创建它
value = counts['missing']  # Creates 'missing' with value 0
print('missing' in counts)  # Output: True
 
# 若想在不创建键的情况下检查,使用 'in' 或 .get()
counts2 = defaultdict(int)
print('missing' in counts2)      # Output: False (doesn't create key)
print(counts2.get('missing'))    # Output: None (doesn't create key)

39.5) (可选)有用的迭代工具

itertools 模块提供用于创建高效迭代器的函数。这些工具帮助你以强大的方式处理序列,而无需创建很大的中间列表。

39.5.1) 使用 chain() 串联可迭代对象

chain() 函数将多个可迭代对象组合为一个迭代器,按顺序从每个可迭代对象中产出元素。

chain() 接受的内容:

  • 多个可迭代对象(列表、元组、字符串等),作为单独参数传入

chain() 返回的内容:

  • 一个迭代器:先产出第一个可迭代对象的所有元素,再产出第二个的所有元素,依此类推

关键优势:

  • 比用 + 拼接更省内存(不会创建中间列表)
  • 适用于任何可迭代对象,不仅是列表
python
from itertools import chain
 
# 合并多个列表
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
 
combined = chain(list1, list2, list3)
print(list(combined))  # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

这比用 + 拼接列表更省内存,尤其是在处理大型序列时:

python
from itertools import chain
 
# 在不创建大型合并列表的情况下处理多个数据源
students_class_a = ["Alice", "Bob", "Charlie"]
students_class_b = ["Diana", "Eve", "Frank"]
students_class_c = ["Grace", "Henry", "Iris"]
 
# 在不创建合并列表的情况下遍历所有学生
for student in chain(students_class_a, students_class_b, students_class_c):
    print(f"Processing: {student}")
# Output:
# Processing: Alice
# Processing: Bob
# Processing: Charlie
# Processing: Diana
# Processing: Eve
# Processing: Frank
# Processing: Grace
# Processing: Henry
# Processing: Iris

你可以串联不同类型的可迭代对象:

python
from itertools import chain
 
# 串联列表、元组与字符串
numbers = [1, 2, 3]
letters = ("a", "b", "c")
word = "xyz"
 
combined = chain(numbers, letters, word)
print(list(combined))  # Output: [1, 2, 3, 'a', 'b', 'c', 'x', 'y', 'z']

39.5.2) 使用 cycle() 重复元素

cycle() 函数创建一个无限迭代器,会反复循环产出某个可迭代对象的元素。

cycle() 接受的内容:

  • 一个可迭代对象(列表、元组、字符串等)

cycle() 返回的内容:

  • 一个无限迭代器,会重复产出可迭代对象中的元素
  • 当到达末尾后,会从头开始

关键特性:

  • 创建一个无限(infinite)迭代器——不会自行停止
  • 必须配合停止条件(计数器、breakzip()
  • 省内存:不会创建数据副本
python
from itertools import cycle
 
# 创建一个无限循环的颜色序列
colors = cycle(["red", "green", "blue"])
 
# 取前 10 个颜色
for i, color in enumerate(colors):
    if i >= 10:
        break
    print(f"Item {i}: {color}")
# Output:
# Item 0: red
# Item 1: green
# Item 2: blue
# Item 3: red
# Item 4: green
# Item 5: blue
# Item 6: red
# Item 7: green
# Item 8: blue
# Item 9: red

警告cycle() 会创建一个无限迭代器。务必配合停止条件(比如计数器或 break 语句)使用,否则你会创建一个无限循环。

一个实用用例是在不同值之间交替:

python
from itertools import cycle
 
# 在表格行之间交替使用两种背景色
row_colors = cycle(["white", "lightgray"])
 
rows = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]
for row, color in zip(rows, row_colors):
    print(f"{row}: background-color: {color}")
# Output:
# Row 1: background-color: white
# Row 2: background-color: lightgray
# Row 3: background-color: white
# Row 4: background-color: lightgray
# Row 5: background-color: white

这里我们使用 zip()(我们在第 37 章学过)把每一行与一种颜色配对。cycle() 迭代器会根据需要自动重复颜色。

39.5.3) 组合 chain() 与 cycle()

你可以组合 itertools 函数来形成更复杂的模式:

python
from itertools import chain, cycle
 
# 创建一个在多个序列之间循环的模式
pattern1 = [1, 2, 3]
pattern2 = [10, 20]
 
# 先串联这些模式,然后对结果进行循环
combined_pattern = cycle(chain(pattern1, pattern2))
 
# 取前 12 个值
for i, value in enumerate(combined_pattern):
    if i >= 12:
        break
    print(value, end=" ")
# Output: 1 2 3 10 20 1 2 3 10 20 1 2
 
print()  # Newline

这会创建一个重复模式:1、2、3、10、20、1、2、3、10、20、...

下面是一个创建轮换排班表的实用示例:

python
from itertools import cycle
 
# 为团队成员创建轮换排班
team_members = ["Alice", "Bob", "Charlie"]
schedule = cycle(team_members)
 
# 轮换给团队成员分配任务
tasks = [
    "Review code",
    "Write tests",
    "Update documentation",
    "Fix bug #123",
    "Implement feature X",
    "Deploy to staging"
]
 
print("Task Assignments:")
for task, assignee in zip(tasks, schedule):
    print(f"  {assignee}: {task}")
# Output:
# Task Assignments:
#   Alice: Review code
#   Bob: Write tests
#   Charlie: Update documentation
#   Alice: Fix bug #123
#   Bob: Implement feature X
#   Charlie: Deploy to staging

itertools 模块

chain: 合并可迭代对象

cycle: 无限重复

比 + 更省内存

适用于不同类型

创建无限迭代器

务必配合停止条件使用

适用于交替模式


在本章中,我们探索了五个扩展 Python 能力的必备标准库模块:

  • random:生成随机数、进行随机选择并打乱序列——对仿真、游戏与测试至关重要
  • datetime:处理日期、时间与持续量——计算年龄、安排事件并格式化时间戳
  • json:使用通用的 JSON 格式与其他程序交换数据——保存应用状态、处理 Web API 并存储配置
  • collections:使用专用容器,例如用于计数的 Counter 与用于自动创建键的 defaultdict
  • itertools:用 chain() 合并序列、用 cycle() 重复模式,从而创建高效迭代器

这些模块都是 Python 标准库的一部分——它们始终可用、经过充分测试,并能优雅地解决常见编程问题。随着你构建更复杂的程序,你会发现自己会频繁地使用这些工具。它们体现了 Python “自带电池(batteries included)”的理念——为日常编程任务提供强大且开箱即用的解决方案。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai