嘿,Python学习搭子!有没有过这种经历——写了个程序,运行得贼顺,结果一打开一个不存在的文件,程序直接原地崩溃,留下一串看不懂的红色错误信息?又或者辛辛苦苦写了半天代码,结果电脑一关机,数据全没了?
别慌,这都是“文件操作”和“异常处理”没学好的典型症状。今天,咱们就来搞定这两个Python开发中绕不开的核心技能。学完这一周,你将能够:
- 1. 优雅地读写文件——不管是文本还是二进制,都能轻松拿捏
- 2. 智能处理路径——用现代化的
pathlib告别繁琐的字符串拼接 - 3. 掌控程序异常——让程序面对错误时从容不迫,而不是直接崩溃
- 4. 构建健壮应用——写出既稳定又好维护的Python代码
为什么需要它?程序运行时的数据都在内存里,关掉就没了。文件就像硬盘上的“记事本”,能让数据永久保存下来。
# 最基础的写文件withopen('日记.txt', 'w', encoding='utf-8') as f: f.write('2026年2月12日 晴\n') f.write('今天学会了Python文件操作!\n')# 最基础的读文件withopen('日记.txt', 'r', encoding='utf-8') as f: content = f.read()print(content)
- •
encoding='utf-8':指定编码,避免中文乱码
- • ❌ 错误1:忘记
with open(),直接f = open()然后忘了关闭 - • ✅ 解决:养成用
with语句的好习惯,它会自动关闭文件
- • ❌ 错误2:文件路径写错,导致
FileNotFoundError - • ✅ 解决:先用
os.path.exists()检查文件是否存在
- • ❌ 错误3:读写二进制文件时用了文本模式,或者反过来
- • ✅ 解决:二进制文件用
'rb'或'wb',文本文件用'r'或'w'
为什么需要它?不是所有文件都是纯文本。图片、视频、压缩包等都是二进制格式,需要特殊处理。
# 复制一张图片(二进制读写)withopen('原始图片.jpg', 'rb') as src_file:withopen('备份图片.jpg', 'wb') as dst_file:# 分块读取,避免大文件占用太多内存 chunk_size = 1024 * 1024# 1MBwhileTrue: chunk = src_file.read(chunk_size)ifnot chunk:break dst_file.write(chunk)
为什么需要它?不同操作系统路径分隔符不同(Windows用\,macOS/Linux用/),手动拼接容易出错。
import os# 拼接路径file_path = os.path.join('data', 'logs', 'app.log')# 获取文件名filename = os.path.basename(file_path) # 'app.log'# 获取目录名dirname = os.path.dirname(file_path) # 'data/logs'# 检查是否存在if os.path.exists(file_path):print('文件存在!')
from pathlib import Path# 创建Path对象(推荐!)log_file = Path('data') / 'logs' / 'app.log'# 链式操作,超级直观if log_file.exists():print(f'文件大小:{log_file.stat().st_size} 字节')print(f'父目录:{log_file.parent}')print(f'文件名:{log_file.name}')print(f'后缀:{log_file.suffix}') # '.log'# 创建目录(自动创建父目录)log_file.parent.mkdir(parents=True, exist_ok=True)
- 1. 更直观:用
/代替os.path.join(),像操作普通路径一样 - 2. 面向对象:所有操作都是方法调用,更符合Python风格
为什么需要它?程序运行时总会遇到意外:文件不存在、网络断开、用户输入错误……异常处理就是程序的“安全气囊”。
try:# 可能出错的代码withopen('不存在的文件.txt', 'r') as f: content = f.read()except FileNotFoundError:# 文件不存在时的处理print('文件不存在,已创建新文件')withopen('不存在的文件.txt', 'w') as f: f.write('这是新建的文件')
完整结构:try-except-else-finally
defread_config(config_file):"""读取配置文件,带完整异常处理""" config = {}try:withopen(config_file, 'r') as f: config_data = f.read()# 这里假设解析配置可能出错 config = eval(config_data) # 实际项目别用eval!这里只是示例except FileNotFoundError:print(f'配置文件 {config_file} 不存在')returnNoneexcept SyntaxError:print(f'配置文件语法错误')returnNoneexcept Exception as e:print(f'未知错误:{e}')returnNoneelse:print('配置文件读取成功!')return configfinally:print('配置读取流程结束')# 清理资源,比如关闭数据库连接等
| | |
FileNotFoundError | | |
PermissionError | | |
IsADirectoryError | | |
UnicodeDecodeError | | |
ValueError | | |
KeyError | | |
IndexError | | |
- 1. 精准捕获:尽量捕获具体异常,而不是笼统的
Exception - 2. 避免裸except:
except:会捕获所有异常,可能隐藏bug - 3. 异常信息友好:给用户看的错误信息要易懂,技术细节写日志
- 4. 资源清理放finally:确保文件、网络连接等资源被正确关闭
5. 上下文管理器(with语句):Pythonic的优雅之道
为什么需要它?文件操作完要关闭,数据库连接用完要释放……这些“善后工作”很容易被忘记。with语句帮你自动处理。
# with语句实际上是这样工作的file_obj = open('test.txt', 'w')try: file_obj.write('Hello')finally: file_obj.close() # 确保一定执行
classTimer:"""计时器上下文管理器"""def__enter__(self):import timeself.start = time.time()returnselfdef__exit__(self, exc_type, exc_val, exc_tb):import timeself.end = time.time()print(f'耗时:{self.end - self.start:.2f}秒')returnFalse# 不压制异常# 使用示例with Timer() as t:import time time.sleep(1)print('睡了一秒钟')
"""文件加密解密工具支持对文本文件进行简单的异或加密"""import sysfrom pathlib import Pathdefxor_encrypt_decrypt(data: bytes, key: int) -> bytes:"""简单的异或加密/解密"""returnbytes([b ^ key for b in data])defprocess_file(input_file: str, output_file: str, key: int = 42):""" 处理文件(加密或解密) 参数: input_file: 输入文件路径 output_file: 输出文件路径 key: 加密密钥(0-255) """ input_path = Path(input_file) output_path = Path(output_file)# 验证输入文件存在ifnot input_path.exists():print(f'错误:输入文件 {input_file} 不存在')returnFalsetry:# 读取文件(支持文本和二进制)if input_path.suffix in ['.txt', '.md', '.py']: mode = 'r'withopen(input_file, mode, encoding='utf-8') as f: content = f.read() data = content.encode('utf-8')else: mode = 'rb'withopen(input_file, mode) as f: data = f.read()# 加密/解密 processed_data = xor_encrypt_decrypt(data, key)# 保存结果if mode == 'r':withopen(output_file, 'w', encoding='utf-8') as f: f.write(processed_data.decode('utf-8'))else:withopen(output_file, 'wb') as f: f.write(processed_data)print(f'✓ 文件处理完成:{output_file}')returnTrueexcept PermissionError:print(f'错误:没有权限访问文件')except Exception as e:print(f'未知错误:{e}')returnFalsedefmain():"""命令行入口"""iflen(sys.argv) < 4:print('用法:python file_crypto.py <输入文件> <输出文件> <密钥>')print('示例:python file_crypto.py secret.txt encrypted.txt 123')return input_file = sys.argv[1] output_file = sys.argv[2]try: key = int(sys.argv[3]) % 256# 确保密钥在0-255范围内except ValueError:print('错误:密钥必须是整数')return process_file(input_file, output_file, key)if __name__ == '__main__': main()
"""日志分析脚本从应用日志中提取错误信息并生成统计报告"""import refrom pathlib import Pathfrom datetime import datetimefrom collections import defaultdict, CounterclassLogAnalyzer:"""日志分析器"""# 常见的日志级别模式 LEVEL_PATTERNS = {'ERROR': r'ERROR|错误|失败|异常','WARNING': r'WARNING|警告|注意','INFO': r'INFO|信息|正常' }# 常见错误类型模式 ERROR_PATTERNS = {'连接超时': r'Timeout|超时|连接失败','内存不足': r'内存不足|OutOfMemory|MemoryError','文件不存在': r'FileNotFound|文件不存在','权限拒绝': r'Permission denied|权限拒绝','语法错误': r'SyntaxError|语法错误' }def__init__(self, log_file):self.log_file = Path(log_file)self.stats = defaultdict(Counter)self.error_details = []defanalyze(self):"""分析日志文件"""ifnotself.log_file.exists():print(f'错误:日志文件 {self.log_file} 不存在')returnFalsetry:withopen(self.log_file, 'r', encoding='utf-8') as f:for line_num, line inenumerate(f, 1):self._process_line(line.strip(), line_num)self._generate_report()returnTrueexcept UnicodeDecodeError:# 尝试其他编码try:withopen(self.log_file, 'r', encoding='gbk') as f:for line_num, line inenumerate(f, 1):self._process_line(line.strip(), line_num)self._generate_report()returnTrueexcept:print(f'错误:无法解码日志文件')returnFalsedef_process_line(self, line, line_num):"""处理单行日志"""ifnot line:return# 检查日志级别for level, pattern inself.LEVEL_PATTERNS.items():if re.search(pattern, line, re.IGNORECASE):self.stats['levels'][level] += 1# 如果是错误级别,进一步分析if level == 'ERROR':self._analyze_error(line, line_num)breakdef_analyze_error(self, line, line_num):"""分析错误行""" error_info = {'line': line_num,'content': line[:200], # 只保存前200个字符'timestamp': self._extract_timestamp(line),'type': '未知错误' }# 匹配错误类型for error_type, pattern inself.ERROR_PATTERNS.items():if re.search(pattern, line, re.IGNORECASE): error_info['type'] = error_typeself.stats['error_types'][error_type] += 1breakself.error_details.append(error_info)def_extract_timestamp(self, line):"""尝试从日志行中提取时间戳"""# 常见的时间格式模式 time_patterns = [r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', # 2026-02-12 10:30:45r'\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}', # 02/12/2026 10:30:45r'\d{2}:\d{2}:\d{2}'# 10:30:45 ]for pattern in time_patterns:match = re.search(pattern, line)ifmatch:returnmatch.group()return'未知时间'def_generate_report(self):"""生成分析报告""" report_file = self.log_file.parent / f'{self.log_file.stem}_分析报告.txt'withopen(report_file, 'w', encoding='utf-8') as f: f.write('=' * 60 + '\n') f.write('日志分析报告\n') f.write(f'生成时间:{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n') f.write(f'分析文件:{self.log_file.name}\n') f.write('=' * 60 + '\n\n')# 1. 日志级别统计 f.write('1. 日志级别分布:\n') total_lines = sum(self.stats['levels'].values())for level, count inself.stats['levels'].most_common(): percentage = (count / total_lines * 100) if total_lines > 0else0 f.write(f' {level}: {count} 行 ({percentage:.1f}%)\n')# 2. 错误类型统计 f.write('\n2. 错误类型分布:\n')for error_type, count inself.stats['error_types'].most_common(): f.write(f' {error_type}: {count} 次\n')# 3. 详细错误列表ifself.error_details: f.write('\n3. 详细错误列表(最近10个):\n')for error inself.error_details[-10:]: f.write(f' 行号 {error["line"]} | 时间 {error["timestamp"]}\n') f.write(f' 类型:{error["type"]}\n') f.write(f' 内容:{error["content"]}\n') f.write(' ' + '-' * 50 + '\n') f.write('\n' + '=' * 60 + '\n') f.write('报告结束\n')print(f'✓ 分析报告已生成:{report_file}')defmain():"""命令行入口"""import sysiflen(sys.argv) < 2:print('用法:python log_analyzer.py <日志文件路径>')print('示例:python log_analyzer.py app.log')return analyzer = LogAnalyzer(sys.argv[1])if analyzer.analyze():print('日志分析完成!')else:print('日志分析失败')if __name__ == '__main__': main()
- 1. 使用
with open('data.txt', 'r') as f:打开文件后,何时会自动关闭文件? - 3. 使用
pathlib时,如何创建Path对象表示data/logs/app.log? - • A)
Path('data', 'logs', 'app.log') - • B)
Path('data') / 'logs' / 'app.log' - • C)
Path.join('data', 'logs', 'app.log')
- 4. 当文件不存在时,
open('nonexistent.txt', 'r')会抛出什么异常? - 5. 以下哪个
try-except结构是最佳实践? - • A)
try: ... except: ... - • B)
try: ... except Exception: ... - • C)
try: ... except FileNotFoundError: ...
- 7. 以下哪个方法可以安全地获取字典值,避免
KeyError? - • D)
dict.value(key)正确答案:B
- 8. 使用
os.path.join('dir', 'subdir', 'file.txt')在Windows上会生成什么路径? - • C)
dir//subdir//file.txt
- 9. 读取大文件时,推荐使用哪种方式避免内存不足?
- • C) 分块读取(如
while chunk: f.read(8192))
- • A)
with open('a.txt') as f1, open('b.txt') as f2: ... - • B)
with open('a.txt') as f1 and open('b.txt') as f2: ... - • C)
with (open('a.txt') as f1, open('b.txt') as f2): ...
任务要求:编写一个Python脚本,实现以下功能:
- 1. 备份指定目录:将指定目录下的所有文件备份到另一个位置
- 5. 异常处理:处理各种可能的错误(权限不足、磁盘空间不足等)
- • 使用
json保存备份元数据(记录文件修改时间) - • 添加命令行参数支持(如
python backup.py --source ./docs --target ./backups)
- 2. 支持定时自动备份(使用
schedule模块或系统cron) - 4. 支持备份到云存储(如AWS S3、Google Drive)
#!/usr/bin/env python3"""自动备份脚本"""import argparsefrom pathlib import Pathimport shutilimport jsonfrom datetime import datetimeimport sysclassBackupManager:def__init__(self, source_dir, backup_root):self.source_dir = Path(source_dir)self.backup_root = Path(backup_root)self.metadata_file = self.backup_root / 'backup_metadata.json'defrun(self):# TODO: 实现备份逻辑passif __name__ == '__main__':# TODO: 解析命令行参数# TODO: 创建BackupManager实例并运行pass
这一周,咱们一起攻克了Python文件操作和异常处理两大核心技能。来,用一张表格总结一下关键收获:
| | |
| 文件读写 | | |
| 路径处理 | os.path | |
| 异常处理 | try-except-else-finally | |
| 上下文管理器 | with | |
- 1. 永远用
with open(),让Python自动帮你关闭文件 - 3. 精准捕获异常,给用户友好的错误提示而不是崩溃
- 1. 动手!动手!动手! 文件操作和异常处理最需要实践,把示例代码都跑一遍
- 2. 故意制造错误:试着删除正在读取的文件,或者用错误编码打开文件,看看异常怎么处理
- 3. 应用到旧项目:回头看看前几周写的代码,哪些地方可以加上异常处理让它更健壮?
感觉怎么样?文件操作和异常处理是不是让你写的程序突然“专业”了好多?别急着庆祝,下一周咱们要进入Python编程的另一个重要里程碑——面向对象编程(OOP)!
- • 咱们会用面向对象的方式重构之前的学生成绩管理系统
- • 学会如何设计类的层次结构,让代码既清晰又易扩展
面向对象是Python进阶的必经之路,也是区分“脚本小子”和“真正开发者”的重要标志。别担心,咱们还是会用“Python学习搭子”的亲切方式,一步步带你掌握这个看似复杂的概念。
记住,文件操作和异常处理不是“高级功能”,而是每个合格Python开发者必备的基本功。今天学的内容,在你未来的每一个项目中都会用到。
如果练习中遇到问题,或者想分享你的备份脚本,随时可以回来讨论。咱们下周见!