📦 模块和包——代码的"乐高积木"
🤔 什么是封装?
先来回顾一下我们学过的各种"封装":
| | |
|---|
| | |
| | def calculate(): |
| | class Student: |
| | |
| | |
一句话理解模块:模块就是程序!每一个 .py 文件都是一个独立的模块。
💡 想象你在玩乐高:函数是小积木块,类是组合好的小组件,模块是完整的部件包,包就是装满部件的盒子!
🎮 实战:把猜数字游戏变成模块
假设我们把猜数字游戏保存为 猜数字.py:
import random def get_valid_input(): """获取有效的数字输入""" input_string = input("设了个数字,你来猜猜看:") while not input_string.isdigit(): input_string = input("❌ 输入错误!请输入正确的数字:") return int(input_string) def game(): """猜数字游戏主函数""" secret_number = random.randint(0, 10) guess_number = get_valid_input() while guess_number != secret_number: if guess_number > secret_number: print("📉 不好意思,您猜大了") else: print("📈 您猜的有点小") guess_number = get_valid_input() print("🎉 大吉大利,今晚吃鸡!") # 如果这个文件直接运行,就启动游戏 if __name__ == "__main__": game()
🎯 在另一个文件中使用这个模块:
创建 玩猜数字.py,放在同一文件夹下:
import 猜数字 print("=== 开始游戏 ===") 猜数字.game() # 调用模块中的函数运行结果:
=== 开始游戏 === 设了个数字,你来猜猜看:5 📈 您猜的有点小 ... 🎉 大吉大利,今晚吃鸡!
💡 关键点:import 猜数字 导入模块后,通过 猜数字.函数名() 调用模块中的函数!
🧩 模块的核心特性
1️⃣ 模块是什么?
📄 每个 .py 文件就是一个独立的模块
🔄 可以被其他程序 import 导入使用
🌍 模块名就是文件名(去掉 .py)
2️⃣ 为什么要用模块?
3️⃣ Python模块的三种类型
┌─────────────────────────────────────────────────────────┐ │ Python 模块分类 │ ├──────────────┬─────────────────┬────────────────────────┤ │ 内置模块 │ 第三方模块 │ 自定义模块 │ │ (标准库) │ (PyPI) │ (自己写的) │ ├──────────────┼─────────────────┼────────────────────────┤ │ • random │ • numpy │ • 猜数字.py │ │ • time │ • pandas │ • my_utils.py │ │ • math │ • requests │ • 任何 .py 文件 │ │ • json │ • django │ │ │ • os │ • flask │ │ ├──────────────┼─────────────────┼────────────────────────┤ │ 安装即自带 │ pip install xxx │ 自己编写 │ └──────────────┴─────────────────┴────────────────────────┘
4️⃣ ⚠️ 重要注意事项
# ❌ 不要这样做!会覆盖系统模块 # 创建一个叫 random.py 的文件 import random # 导入的是你自己的 random.py,不是系统的! # ✅ 正确做法:模块名要有意义且不与系统模块冲突 # my_random.py ✓ # utils.py ✓ # random.py ✗ (系统已有) # sys.py ✗ (系统已有)
5️⃣ 🔍 模块搜索路径
当 Python 执行 import xxx 时,会按以下顺序查找:
1️⃣ 当前目录(运行程序的文件夹) ↓ 2️⃣ PYTHONPATH 环境变量中的目录 ↓ 3️⃣ Python 安装目录的 Lib 文件夹
查看搜索路径:
import sys print(sys.path) # 打印所有搜索路径
🏷️ 命名空间——解决"撞名"问题
🤔 什么是命名空间?
命名空间(Namespace) = 标识符的"活动范围"
💡 生活例子:太阳希望小学1班有个叫"小花"的同学,2班也有个叫"小花"的同学。老师在班里点名"小花"没问题,但期末考试排名时,需要说"1班的小花"和"2班的小花"才能区分。
班级 = 命名空间
🎯 Python中的命名空间
每个模块都有自己的独立命名空间,就像不同的班级:
# 模块A:math_utils.py def add(a, b): return a + b # 模块B:string_utils.py def add(str1, str2): return str1 + str2 # 使用时要加上"班级名"(模块名) import math_utils import string_utils result1 = math_utils.add(1, 2) # 3 result2 = string_utils.add("Hello", "World") # "HelloWorld"关键点:即使函数名相同,只要模块不同,就不会冲突!
📥 模块导入的三种姿势
方式一:import 模块名
import math print(math.pi) # 3.141592653589793 print(math.sqrt(16)) # 4.0
✅ 优点:明确知道函数来自哪个模块 ❌ 缺点:每次都要写模块名,有点长
方式二:from 模块名 import 函数名
from math import pi, sqrt print(pi) # 直接写名字,不用加 math. print(sqrt(16)) # 4.0
✅ 优点:代码更简洁 ❌ 缺点:不知道函数来自哪个模块,容易命名冲突
方式三:import 模块名 as 别名
import numpy as np # 科学计算库,名字太长,起个别名 import pandas as pd arr = np.array([1, 2, 3]) # 用别名调用
✅ 优点:既简洁又明确 ❌ 缺点:需要记住别名约定(如 np、pd)
🎯 实际应用示例
# 假设有个工具模块 utils.py def add(a, b): return a + b def multiply(a, b): return a * b
# 使用方式对比 # 方式1:推荐!最清晰 import utils result = utils.add(1, 2) # 方式2:适合只用一两个函数 from utils import add result = add(1, 2) # 方式3:模块名太长时用 import very_long_module_name as vlmn result = vlmn.some_function()
💡 建议:团队开发推荐方式1,个人小脚本可以用方式2
🌐 第三方模块——Python的"应用商店"
Python 拥有世界上最丰富的第三方库生态!访问 PyPI 可以查看所有开源模块。
📦 常用 pip 命令速查
# ========== 安装模块 ========== pip install 模块名 # 安装模块 pip install 模块名==1.2.3 # 安装指定版本 pip install -U 模块名 # 升级模块 (U = Upgrade) # ========== 卸载模块 ========== pip uninstall 模块名 # 卸载模块 # ========== 查看已安装 ========== pip list # 列出所有已安装模块 pip show 模块名 # 查看模块详细信息 # ========== 使用国内镜像加速 ========== # 默认从国外下载很慢,使用国内镜像源 pip install 模块名 -i https://pypi.tuna.tsinghua.edu.cn/simple # 常用国内镜像源: # 清华:https://pypi.tuna.tsinghua.edu.cn/simple # 阿里云:http://mirrors.aliyun.com/pypi/simple/ # 豆瓣:http://pypi.douban.com/simple/
🎯 常用第三方模块推荐
| | |
|---|
| requests | | pip install requests |
| numpy | | pip install numpy |
| pandas | | pip install pandas |
| matplotlib | | pip install matplotlib |
| flask | | pip install flask |
| django | | pip install django |
| pillow | | pip install pillow |
🔧 离线安装(无网络环境)
# 1. 下载 .whl 或 .tar.gz 文件 # 2. 在文件所在目录执行: # 安装 whl 文件 pip install xxx.whl # 安装源码包 # 先解压,进入目录,然后: python setup.py build python setup.py install
🎁 包——模块的"文件夹"
📁 什么是包?
包就是将多个相关模块组织在一起的文件夹。就像你把相关的文件放在同一个文件夹里一样!
包的结构:
my_package/ ← 包文件夹 ├── __init__.py ← 必须有这个文件(可以是空的) ├── module1.py ← 模块1 ├── module2.py ← 模块2 └── sub_package/ ← 子包 ├── __init__.py └── module3.py
⚠️ 关键:文件夹里必须有 __init__.py 文件,Python 才认为这是一个包!
🎯 导入包的几种方式
# 方式1:导入包中的模块 import my_package.module1 my_package.module1.some_function() # 方式2:从包中导入指定模块 from my_package import module1 module1.some_function() # 方式3:从模块中导入函数 from my_package.module1 import some_function some_function() # 方式4:导入包中所有模块(不推荐) from my_package import *
📚 概念厘清:模块 vs 包 vs 库
💡 记忆口诀:模块是文件,包是文件夹,库是大集合!
📄 文件读写——与硬盘"对话"
🤔 为什么要学文件操作?
想象你在写一篇文章:
💾 保存 = 把内存中的数据写入文件(持久化)
📂 打开 = 从文件中读取数据到内存
程序运行时的数据都在内存中,断电就消失!文件操作让数据可以永久保存。
🔓 打开文件——open() 函数
使用文件前必须先打开,就像读书前要先翻开书一样!
# 基本语法 文件对象 = open(文件名, 模式, encoding='utf-8')
🎯 参数说明
| | |
|---|
filename | | 'data.txt' |
mode | | 'r' |
encoding | | 'utf-8' |
📖 常用打开模式速查表
| | | |
|---|
'r' | | | |
'w' | | | |
'a' | | | |
'x' | | | |
'r+' | | | |
'w+' | | | |
'a+' | | | |
'b' | | | |
💡 记忆技巧:
r = read(读)
w = write(写)
a = append(追加)
+ = 可读可写
b = binary(二进制)
🎯 with 语句——文件操作的"最佳实践"
❌ 传统写法(容易忘记关闭)
f = open('test.txt', 'r') content = f.read() print(content) f.close() # 容易忘记!导致资源泄漏✅ 推荐写法(自动关闭)
with open('test.txt', 'r', encoding='utf-8') as f: content = f.read() print(content) # 出了 with 代码块,文件自动关闭!💡 为什么用 with?
✅ 自动关闭文件,不用担心忘记 close()
✅ 即使发生异常,也会正确关闭文件
✅ 代码更简洁、更 Pythonic
📝 实战:写入九九乘法表
# 把九九乘法表写入文件 with open('九九乘法表.txt', 'w', encoding='utf-8') as f: for i in range(1, 10): for j in range(1, i + 1): f.write(f'{j} × {i} = {i * j:2d} ') f.write('\n') # 每行结束换行 print("✅ 九九乘法表已保存到文件!")生成的文件内容:
1 × 1 = 1 1 × 2 = 2 2 × 2 = 4 1 × 3 = 3 2 × 3 = 6 3 × 3 = 9 ...
📖 完整的 open() 参数说明
open(filename, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
| |
|---|
filename | |
mode | |
buffering | 缓冲模式:0=不缓冲,1=行缓冲,>1=缓冲区大小 |
encoding | |
errors | |
newline | |
closefd | |
opener | |
💡 初学者重点掌握:filename、mode、encoding 三个参数即可!
📖 文件操作方法大全
🔍 读取文件的三种方式
# 假设文件内容: # 第一行 # 第二行 # 第三行 # ========== 方式1:read() —— 读取全部 ========== with open('test.txt', 'r', encoding='utf-8') as f: content = f.read() # 读取整个文件 print(content) # 适合:小文件,需要一次性处理全部内容 # ========== 方式2:readline() —— 逐行读取 ========== with open('test.txt', 'r', encoding='utf-8') as f: line1 = f.readline() # 读取第一行 line2 = f.readline() # 读取第二行 print(line1, line2) # 适合:只需要前几行,或逐行处理大文件 # ========== 方式3:readlines() —— 读取为列表 ========== with open('test.txt', 'r', encoding='utf-8') as f: lines = f.readlines() # ['第一行\n', '第二行\n', '第三行\n'] for line in lines: print(line.strip()) # strip() 去掉换行符 # 适合:需要按行处理,且文件不太大 # ========== 推荐方式:直接遍历文件对象 ========== with open('test.txt', 'r', encoding='utf-8') as f: for line in f: # 最简洁!自动逐行读取 print(line.strip()) # ✅ 适合:所有情况,尤其是大文件(省内存)
✏️ 写入操作
# write() —— 写入字符串 with open('output.txt', 'w', encoding='utf-8') as f: f.write('Hello, World!\n') # 注意手动加换行符 f.write('第二行内容\n') # writelines() —— 写入列表 lines = ['第一行\n', '第二行\n', '第三行\n'] with open('output.txt', 'w', encoding='utf-8') as f: f.writelines(lines)
🎯 文件指针操作
想象文件指针就像书签,标记当前读写到哪个位置:
with open('test.txt', 'r+', encoding='utf-8') as f: # tell() —— 查看当前位置 print(f.tell()) # 0,刚开始在文件开头 content = f.read(5) # 读取5个字符 print(f.tell()) # 5,指针移动到了第5个字符后 # seek() —— 移动指针位置 f.seek(0) # 回到文件开头 f.seek(0, 2) # 跳到文件末尾(用于追加)
📋 文件操作方法速查表
| | |
|---|
read() | | f.read() |
read(size) | | f.read(100) |
readline() | | f.readline() |
readlines() | | f.readlines() |
write(str) | | f.write('hello') |
writelines(list) | | f.writelines(['a\n', 'b\n']) |
tell() | | f.tell() |
seek(pos) | | f.seek(0) |
close() | | f.close() |
🛡️ 异常处理——让程序更"健壮"
🤔 为什么要处理异常?
想象你在开车:
🚗 正常情况:一路绿灯,顺利到达目的地
⚠️ 异常情况:遇到红灯、堵车、轮胎爆胎...
异常处理就像汽车的保险系统——不能阻止问题发生,但能让问题不导致"车毁人亡"(程序崩溃)!
🚨 常见的异常类型
| | |
|---|
ZeroDivisionError | | 10 / 0 |
ValueError | | int("abc") |
FileNotFoundError | | open('不存在的文件.txt') |
IndexError | | lst[10] |
KeyError | | d['不存在的key'] |
TypeError | | '2' + 2 |
🎯 try-except —— 捕获异常的基本语法
try: # 可能出错的代码 可能引发异常的代码 except: # 出错后执行的代码 异常处理代码
执行流程:
┌─────────────┐ │ 执行 try │ │ 中的代码 │ └──────┬──────┘ │ ┌───┴───┐ ▼ ▼ ┌──────┐ ┌────────┐ │正常 │ │发生异常│ └──┬───┘ └────┬───┘ │ │ ▼ ▼ ┌──────┐ ┌──────────┐ │跳过 │ │执行 │ │except│ │except代码 │ └──┬───┘ └────┬─────┘ │ │ └────┬─────┘ ▼ 继续执行后面代码
📝 实战:除法计算器
❌ 没有异常处理(程序会崩溃)
dividend = int(input("请输入被除数:")) divisor = int(input("请输入除数:")) result = dividend / divisor print(f'{dividend} ÷ {divisor} = {result}') # 如果输入除数为0: # ZeroDivisionError: division by zero✅ 有异常处理(程序优雅处理)
try: dividend = int(input("请输入被除数:")) divisor = int(input("请输入除数:")) result = dividend / divisor print(f'{dividend} ÷ {divisor} = {result}') except ZeroDivisionError: print("❌ 错误:除数不能为0!") except ValueError: print("❌ 错误:请输入有效的数字!") except Exception as e: print(f"❌ 发生未知错误:{e}")
🎯 捕获多种异常
方式一:多个 except 代码块
try: # 可能引发多种异常的代码 num = int(input("请输入数字:")) result = 100 / num print(f"结果是:{result}") except ValueError: print("❌ 输入的不是数字!") except ZeroDivisionError: print("❌ 不能除以0!") except Exception as e: print(f"❌ 其他错误:{e}")方式二:一个 except 捕获多种
try: num = int(input("请输入数字:")) result = 100 / num except (ValueError, ZeroDivisionError) as e: print(f"❌ 输入错误:{e}")
🔄 try-except-else-finally 完整结构
try: # 尝试执行的代码 print("尝试打开文件...") f = open('data.txt', 'r') content = f.read() except FileNotFoundError: # 发生异常时执行 print("❌ 文件不存在!") else: # 没有异常时执行(可选) print(f"✅ 读取成功!内容长度:{len(content)}") f.close() finally: # 无论是否异常都执行(可选) print("🏁 程序执行完毕")执行顺序:
try → 正常? → else → finally ↓ 异常? → except → finally
💡 finally 的应用场景:关闭文件、关闭数据库连接、释放锁等清理工作
🚀 主动抛出异常 —— raise
有时候你需要主动告诉调用者:"这里出错了!"
def divide(a, b): if b == 0: raise ZeroDivisionError("除数不能为0!") return a / b # 使用 try: result = divide(10, 0) except ZeroDivisionError as e: print(f"捕获到错误:{e}")自定义异常:
class ValidationError(Exception): """自定义验证错误""" pass def set_age(age): if age < 0: raise ValidationError("年龄不能为负数!") if age > 150: raise ValidationError("年龄不能超过150!") return age
🛡️ 异常处理最佳实践
# ✅ 好的做法:具体捕获,提供有用信息 try: with open('data.txt', 'r', encoding='utf-8') as f: data = json.load(f) except FileNotFoundError: print("❌ 数据文件不存在,将使用默认配置") data = {} except json.JSONDecodeError: print("❌ 数据文件格式错误,请检查JSON格式") data = {} # ❌ 不好的做法:捕获所有异常但不处理 try: do_something() except: # 捕获所有异常但什么都不做 pass # 静默吞掉错误,难以调试!📝 文档总结
一、核心知识点回顾
1. 模块(Module)——代码的"积木块"
核心概念:
📄 每个 .py 文件就是一个模块
🔄 可以被其他程序 import 导入使用
🎯 实现了代码的封装和复用
三种导入方式:
| | |
|---|
| import math | |
| from math import sqrt | |
| import numpy as np | |
模块类型:
2. 包(Package)——模块的"文件夹"
核心概念:
概念对比:
3. 文件操作——数据的"持久化"
打开模式速查:
推荐写法(with语句):
# ✅ 推荐:自动关闭文件 with open('file.txt', 'r', encoding='utf-8') as f: content = f.read() # ❌ 不推荐:容易忘记close() f = open('file.txt', 'r') content = f.read() f.close()常用方法:
4. 异常处理——程序的"保险丝"
基本结构:
try: # 可能出错的代码 可能引发异常的代码 except 具体异常类型: # 异常处理 处理代码 else: # 没有异常时执行(可选) finally: # 无论是否异常都执行(可选)
常见异常类型:
| |
|---|
ZeroDivisionError | |
ValueError | |
FileNotFoundError | |
IndexError | |
KeyError | |
最佳实践:
✅ 捕获具体异常,不要裸 except:
✅ 提供有用的错误信息
✅ 使用 finally 释放资源
❌ 不要静默吞掉异常
二、知识图谱
Python代码组织与IO操作 │ ├── 📦 模块与包 │ ├── 模块(.py文件) │ │ ├── 导入方式:import / from...import / as │ │ ├── 内置模块:random, time, json... │ │ ├── 第三方模块:pip install │ │ └── 自定义模块:自己写的.py │ │ │ └── 包(文件夹) │ ├── __init__.py 标记 │ └── 层级导入:import pkg.module │ ├── 📄 文件操作 │ ├── 打开:open(filename, mode, encoding) │ ├── 模式:r/w/a/r+/b │ ├── 读取:read/readline/readlines │ ├── 写入:write/writelines │ └── with语句:自动关闭 │ └── 🛡️ 异常处理 ├── try-except:捕获异常 ├── else:无异常时执行 ├── finally:必定执行 └── raise:主动抛出
三、学习建议
模块是重用代码的基础:把常用的函数放到模块里,到处都能用
文件操作记住 with:既安全又简洁,是 Python 的惯用法
异常处理要具体:捕获具体的异常类型,提供有用的错误信息
编码问题要注意:文件操作务必指定 encoding='utf-8'
四、下篇预告
下一篇我们将学习:
这些都是 Python 的高级特性,能让你的代码更加优雅高效!
🎮 动手练一练
学完不练,等于白学!下面几道题目帮你巩固今天学到的知识。
选择题
1. 以下关于模块导入的说法,正确的是?
💡 答案解析(点击展开)
正确答案:B
详细解析:
选项 A 错误:import math 使用时需要 math.sqrt(),而 from math import * 可以直接用 sqrt(),但后者会导入所有名称,可能引发命名冲突。
选项 B 正确:as 关键字用于给模块或函数起别名,np 是 numpy 的常用别名,可以简化代码书写。
选项 C 错误:绝对不要自定义与系统模块同名的模块!比如创建 random.py 会覆盖系统的 random 模块,导致 import random 导入的是你自己的文件。
选项 D 错误:导入模块时,模块中的顶层代码会立即执行(函数定义不会执行,但函数外的 print 等会执行)。
记忆技巧:import A as B 就像给好朋友起外号,叫起来更方便!
</details>
2. 以下代码执行后,文件内容是什么?
with open('test.txt', 'w', encoding='utf-8') as f: f.write('Hello') f.write('World')A. HelloWorld
B. Hello\nWorld
C. Hello World
D. 报错
💡 答案解析(点击展开)
正确答案:A
详细解析:
write() 方法不会自动添加换行符,需要手动写 \n。
代码中先写 Hello,再写 World,两者会连在一起。
如果要换行,应该写成:f.write('Hello\n') 或 f.write('Hello') 后 f.write('\n')。
代码验证:
with open('test.txt', 'w', encoding='utf-8') as f: f.write('Hello') f.write('World') # 文件内容:HelloWorld(没有换行) with open('test.txt', 'w', encoding='utf-8') as f: f.write('Hello\n') f.write('World') # 文件内容: # Hello # World易错点:很多初学者以为 write() 会自动换行,记住:write 不会加换行,print 才会!
</details>
3. 以下关于异常处理的说法,错误的是?
A. try-except 可以捕获并处理异常,防止程序崩溃
B. finally 代码块无论是否发生异常都会执行
C. 使用裸 except: 可以捕获所有异常,是最佳实践
D. raise 语句可以主动抛出异常
💡 答案解析(点击展开)
正确答案:C
详细解析:
选项 A 正确:这是异常处理的核心作用,让程序优雅地处理错误而不是直接崩溃。
选项 B 正确:finally 块用于释放资源,无论 try 中是否发生异常,finally 都会执行。
选项 C 错误:裸 except: 虽然能捕获所有异常,但会捕获包括 KeyboardInterrupt(Ctrl+C)和 SystemExit 在内的所有异常,不利于调试,也不是最佳实践。应该捕获具体的异常类型。
# ❌ 不推荐 try: do_something() except: pass # 静默吞掉所有错误,难以调试 # ✅ 推荐 try: do_something() except ValueError as e: print(f"值错误:{e}") except FileNotFoundError: print("文件不存在")选项 D 正确:raise ValueError("错误信息") 可以主动抛出异常,用于参数校验等场景。
最佳实践原则:具体捕获、提供信息、优雅降级、不要静默。
</details>
编程题
1. 创建一个简单的学生信息管理模块,包含添加学生、显示所有学生、保存到文件、从文件读取等功能。
💡 提示:使用 json 模块来保存和读取数据,使用 with 语句操作文件。
🔑 参考答案与解析(点击展开)
解题思路:
创建一个 student_manager.py 模块
使用列表存储学生信息(每个学生是字典)
实现添加、显示、保存、读取四个功能
使用 json 模块进行数据持久化
添加异常处理,使程序更健壮
代码实现:
# student_manager.py """学生信息管理模块""" import json import os # 数据文件路径 DATA_FILE = 'students.json' # 内存中的学生列表 students = [] def add_student(name, age, score): """添加学生""" student = { 'name': name, 'age': age, 'score': score } students.append(student) print(f"✅ 已添加学生:{name}") def show_all_students(): """显示所有学生""" if not students: print("📭 暂无学生信息") return print("\n" + "=" * 50) print(f"{'姓名':<10}{'年龄':<10}{'成绩':<10}") print("-" * 50) for s in students: print(f"{s['name']:<10}{s['age']:<10}{s['score']:<10}") print("=" * 50) def save_to_file(): """保存到文件""" try: with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump(students, f, ensure_ascii=False, indent=2) print(f"✅ 数据已保存到 {DATA_FILE}") except Exception as e: print(f"❌ 保存失败:{e}") def load_from_file(): """从文件读取""" global students if not os.path.exists(DATA_FILE): print(f"📭 文件 {DATA_FILE} 不存在,将创建新文件") students = [] return try: with open(DATA_FILE, 'r', encoding='utf-8') as f: students = json.load(f) print(f"✅ 已加载 {len(students)} 条学生记录") except json.JSONDecodeError: print("❌ 文件格式错误,数据可能已损坏") students = [] except Exception as e: print(f"❌ 读取失败:{e}") students = [] def main(): """主函数""" # 启动时加载数据 load_from_file() while True: print("\n" + "=" * 30) print("📚 学生信息管理系统") print("=" * 30) print("1. 添加学生") print("2. 显示所有学生") print("3. 保存数据") print("4. 重新加载数据") print("0. 退出") choice = input("\n请选择操作:") if choice == '1': name = input("请输入姓名:") try: age = int(input("请输入年龄:")) score = float(input("请输入成绩:")) add_student(name, age, score) except ValueError: print("❌ 年龄和成绩必须是数字!") elif choice == '2': show_all_students() elif choice == '3': save_to_file() elif choice == '4': load_from_file() elif choice == '0': # 退出前保存 save_to_file() print("👋 再见!") break else: print("❌ 无效的选择") if __name__ == '__main__': main()使用示例:
# 在另一个文件中使用 import student_manager as sm sm.load_from_file() sm.add_student("张三", 18, 85.5) sm.add_student("李四", 19, 92.0) sm.show_all_students() sm.save_to_file()知识点总结:
json.dump() / json.load():Python对象与JSON文件的转换
os.path.exists():检查文件是否存在
global 关键字:在函数内修改全局变量
if __name__ == '__main__'::模块直接运行时才执行
进阶挑战:
添加删除学生功能
添加按成绩排序功能
添加搜索学生功能
使用类来重构代码
</details>
2. 编写一个函数,统计一个文本文件中每个单词出现的频率,并将结果保存到另一个文件中。
💡 提示:读取文件 → 分割单词 → 统计频率 → 排序 → 写入结果文件。注意处理文件不存在的情况。
🔑 参考答案与解析(点击展开)
解题思路:
使用 with open() 读取源文件
将文本转为小写,使用正则表达式提取单词
用字典统计每个单词的出现次数
按频率排序
将结果写入目标文件
添加异常处理,处理文件不存在等情况
代码实现:
import re from collections import Counter def count_words_in_file(input_file, output_file): """ 统计文件中单词频率并保存结果 参数: input_file: 输入文件路径 output_file: 输出文件路径 """ try: # 1. 读取文件 with open(input_file, 'r', encoding='utf-8') as f: text = f.read() print(f"✅ 成功读取文件:{input_file}") print(f"📄 文件大小:{len(text)} 字符") except FileNotFoundError: print(f"❌ 错误:文件 '{input_file}' 不存在!") return False except UnicodeDecodeError: print(f"❌ 错误:文件编码不正确,请使用 UTF-8 编码!") return False except Exception as e: print(f"❌ 读取文件时发生错误:{e}") return False # 2. 提取单词(使用正则表达式) # \b\w+\b 匹配单词边界之间的字符 words = re.findall(r'\b[a-zA-Z]+\b', text.lower()) if not words: print("⚠️ 警告:未找到任何单词") return False print(f"🔤 找到 {len(words)} 个单词,不重复单词 {len(set(words))} 个") # 3. 统计频率(使用 Counter 更方便) word_counts = Counter(words) # 4. 按频率降序排序 sorted_words = word_counts.most_common() # 5. 写入结果文件 try: with open(output_file, 'w', encoding='utf-8') as f: f.write(f"{'='*50}\n") f.write(f"单词频率统计结果\n") f.write(f"源文件:{input_file}\n") f.write(f"总单词数:{len(words)}\n") f.write(f"不重复单词数:{len(sorted_words)}\n") f.write(f"{'='*50}\n\n") f.write(f"{'排名':<6}{'单词':<20}{'次数':<10}{'频率':<10}\n") f.write('-' * 50 + '\n') for rank, (word, count) in enumerate(sorted_words, 1): frequency = count / len(words) * 100 f.write(f"{rank:<6}{word:<20}{count:<10}{frequency:.2f}%\n") print(f"✅ 统计结果已保存到:{output_file}") # 显示前10个高频词 print("\n📊 高频词 TOP 10:") for word, count in sorted_words[:10]: print(f" {word}: {count} 次") return True except Exception as e: print(f"❌ 写入结果文件时发生错误:{e}") return False # 测试函数 def create_test_file(): """创建测试文件""" test_content = """ Python is a great programming language. Python is easy to learn. I love Python programming. Programming with Python is fun. Python Python Python! """ with open('test_input.txt', 'w', encoding='utf-8') as f: f.write(test_content) print("✅ 测试文件已创建:test_input.txt") if __name__ == '__main__': # 创建测试文件 create_test_file() # 执行统计 count_words_in_file('test_input.txt', 'word_stats.txt') # 测试文件不存在的情况 print("\n" + "="*50) count_words_in_file('not_exist.txt', 'output.txt')运行结果示例:
✅ 测试文件已创建:test_input.txt ✅ 成功读取文件:test_input.txt 📄 文件大小:156 字符 🔤 找到 24 个单词,不重复单词 11 个 ✅ 统计结果已保存到:word_stats.txt 📊 高频词 TOP 10: python: 6 次 is: 3 次 programming: 3 次 a: 1 次 ... ================================================== ❌ 错误:文件 'not_exist.txt' 不存在!
知识点总结:
re.findall(r'\b[a-zA-Z]+\b', text):使用正则提取单词
Counter 类:来自 collections 模块,专门用于计数
most_common():返回频率最高的元素
全面的异常处理:分别处理文件不存在、编码错误等情况
进阶挑战:
</details>
3. 编写一个日志记录装饰器,可以自动记录函数的调用时间、参数和返回值。
💡 提示:使用装饰器包装函数,在函数执行前后记录信息。需要用到 functools.wraps 保留原函数信息。
🔑 参考答案与解析(点击展开)
解题思路:
创建装饰器函数 log_decorator
使用 functools.wraps 保留原函数的元数据
在函数执行前记录:时间、函数名、参数
执行原函数并捕获返回值
在函数执行后记录:返回值、执行耗时
将日志写入文件
添加异常处理,记录函数执行中的异常
代码实现:
import functools import time from datetime import datetime def log_decorator(log_file='function_log.txt'): """ 日志记录装饰器 使用方式: @log_decorator() def my_function(x, y): return x + y """ def decorator(func): @functools.wraps(func) # 保留原函数的元数据 def wrapper(*args, **kwargs): # 记录开始时间 start_time = time.time() start_datetime = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 格式化参数信息 args_str = ', '.join([repr(a) for a in args]) kwargs_str = ', '.join([f"{k}={repr(v)}" for k, v in kwargs.items()]) all_args = ', '.join(filter(None, [args_str, kwargs_str])) # 写入开始日志 log_entry = f""" {'='*60} [{start_datetime}] 函数调用 函数名: {func.__name__} 参数: {all_args} """ try: # 执行原函数 result = func(*args, **kwargs) # 计算执行时间 end_time = time.time() execution_time = (end_time - start_time) * 1000 # 转为毫秒 # 完成日志 log_entry += f"""返回值: {repr(result)} 执行时间: {execution_time:.2f} ms 状态: ✅ 成功 {'='*60} """ print(f"✅ {func.__name__} 执行成功,耗时 {execution_time:.2f} ms") return result except Exception as e: # 记录异常 end_time = time.time() execution_time = (end_time - start_time) * 1000 log_entry += f"""异常类型: {type(e).__name__} 异常信息: {str(e)} 执行时间: {execution_time:.2f} ms 状态: ❌ 失败 {'='*60} """ print(f"❌ {func.__name__} 执行失败:{e}") raise # 重新抛出异常 finally: # 写入日志文件 with open(log_file, 'a', encoding='utf-8') as f: f.write(log_entry) return wrapper return decorator # ========== 使用示例 ========== @log_decorator('math_operations.log') def add(a, b): """加法运算""" time.sleep(0.1) # 模拟耗时操作 return a + b @log_decorator('math_operations.log') def divide(a, b): """除法运算""" time.sleep(0.05) return a / b @log_decorator('data_processing.log') def process_data(data, multiplier=1): """数据处理""" time.sleep(0.2) return [x * multiplier for x in data] # 测试代码 def main(): print("🧪 测试日志装饰器\n") # 测试正常执行 result1 = add(10, 20) print(f"10 + 20 = {result1}\n") result2 = divide(100, 5) print(f"100 / 5 = {result2}\n") result3 = process_data([1, 2, 3, 4, 5], multiplier=2) print(f"process_data 结果: {result3}\n") # 测试异常情况 try: divide(10, 0) except ZeroDivisionError: print("捕获到除零异常(预期行为)\n") print("📄 日志已保存到文件,请查看:") print(" - math_operations.log") print(" - data_processing.log") if __name__ == '__main__': main()生成的日志文件示例(math_operations.log):
============================================================ [2024-01-15 10:30:25] 函数调用 函数名: add 参数: 10, 20 返回值: 30 执行时间: 105.23 ms 状态: ✅ 成功 ============================================================ ============================================================ [2024-01-15 10:30:25] 函数调用 函数名: divide 参数: 100, 5 返回值: 20.0 执行时间: 52.15 ms 状态: ✅ 成功 ============================================================ ============================================================ [2024-01-15 10:30:25] 函数调用 函数名: divide 参数: 10, 0 异常类型: ZeroDivisionError 异常信息: division by zero 执行时间: 0.52 ms 状态: ❌ 失败 ============================================================
知识点总结:
装饰器:@decorator 语法糖,用于扩展函数功能
functools.wraps:保留原函数的 __name__、__doc__ 等属性
*args, **kwargs:接收任意数量和类型的参数
time.time():获取当前时间戳,用于计算执行时间
finally 块:确保日志一定被写入,即使发生异常
进阶挑战:
</details>