配套专栏:Python 全栈修炼之路 第 08 篇《文件操作与异常处理》
难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐
核心覆盖:文件读写、with 上下文管理器、pathlib、异常处理、日志记录、JSON/YAML 配置、目录遍历
题目一:安全的文件复制工具 ⭐
📌 题目描述
编写函数 safe_copy(src, dst, overwrite=False),实现安全的文件复制功能:
# 基础用法
safe_copy(\’data.txt\’, \’backup/data.txt\’) # 复制到备份目录
# 显示进度
safe_copy(\’large.zip\’, \’backup/large.zip\’, progress=True)
# 输出: 复制进度: 45.2%
# 处理异常
safe_copy(\’not_exist.txt\’, \’dest.txt\’) # 源文件不存在,抛出 FileNotFoundError
safe_copy(\’src.txt\’, \’dest.txt\’, overwrite=False) # 目标已存在,抛出 FileExistsError
💡 编程思路
这道题考察 文件读写 + 异常处理 + 目录创建:
🖥️ 参考代码
from pathlib import Path
import shutil
def safe_copy(src, dst, overwrite=False, progress=False, chunk_size=8192):
\”\”\”
安全的文件复制工具
参数:
src: 源文件路径
dst: 目标文件路径
overwrite: 是否覆盖已存在的目标文件
progress: 是否显示复制进度
chunk_size: 分块大小(字节)
异常:
FileNotFoundError: 源文件不存在
FileExistsError: 目标文件已存在且 overwrite=False
PermissionError: 权限不足
\”\”\”
src_path = Path(src)
dst_path = Path(dst)
# 检查源文件
if not src_path.exists():
raise FileNotFoundError(f\”源文件不存在: {
src}\”)
if not src_path.is_file():
raise ValueError(f\”源路径不是文件: {
src}\”)
# 检查目标文件
if dst_path.exists() and not overwrite:
raise FileExistsError(f\”目标文件已存在: {
dst} (使用 overwrite=True 覆盖)\”)
# 创建目标目录
dst_path.parent.mkdir(parents=True, exist_ok=True)
# 获取文件大小用于进度计算
total_size = src_path.stat().st_size
copied_size = 0
# 分块复制
with open(src_path, \’rb\’) as fsrc:
with open(dst_path, \’wb\’) as fdst:
while True:
chunk = fsrc.read(chunk_size)
if not chunk:
break
fdst.write(chunk)
copied_size += len(chunk)
if progress and total_size > 0:
percent = (copied_size / total_size) * 100
print(f\”\\r复制进度: {
percent:.1f}%\”, end=\’\’, flush=True)
if progress:
print() # 换行
return dst_path
def copy_with_metadata(src, dst, overwrite=False):
\”\”\”复制文件并保留元数据(修改时间、权限等)\”\”\”
dst_path = safe_copy(src, dst, overwrite)
# 复制元数据
src_stat = Path(src).stat()
dst_path.touch()
import os
os.utime(dst_path, (src_stat.st_atime, src_stat.st_mtime))
return dst_path
# 测试
if __name__ == \”__main__\”:
import tempfile
import os
# 创建临时测试文件
with tempfile.NamedTemporaryFile(mode=\’w\’, delete=False, suffix=\’.txt\’) as f:
f.write(\”Hello, World!\” * 1000)
temp_src = f.name
try:
# 基础复制
temp_dst = tempfile.mktemp(suffix=\’.txt\’)
result = safe_copy(temp_src, temp_dst)
print(f\”复制成功: {
result}\”)
assert Path(temp_dst).read_text() == Path(temp_src).read_text()
# 测试覆盖检查
try:
safe_copy(temp_src, temp_dst, overwrite=False)
assert False, \”应该抛出 FileExistsError\”
except FileExistsError:
print(\”覆盖检查通过 ✓\”)
# 测试源文件不存在
try:
safe_copy(\’not_exist_xxx.txt\’, \’dest.txt\’)
assert False, \”应该抛出 FileNotFoundError\”
except FileNotFoundError:
print(\”源文件检查通过 ✓\”)
# 清理
os.unlink(temp_dst)
finally:
os.unlink(temp_src)
print(\”\\n所有测试通过 ✓\”)
🔗 关联知识点
| pathlib.Path | 面向对象的路径操作 |
| mkdir(parents=True) | 自动创建父目录 |
| with open(…) | 上下文管理器确保文件关闭 |
| 分块读取 read(chunk_size) | 大文件处理,避免内存溢出 |
| 自定义异常抛出 | raise FileNotFoundError(…) |
题目二:JSON 配置文件管理器 ⭐⭐
📌 题目描述
实现一个 ConfigManager 类,管理 JSON 格式的配置文件:
config = ConfigManager(\’app.json\’)
# 使用点号分隔的键路径
config.set(\’database.host\’, \’localhost\’)
config.set(\’database.port\’, 3306)
config.set(\’database.credentials.username\’, \’admin\’)
# 获取值(支持默认值)
host = config.get(\’database.host\’) # \’localhost\’
ssl = config.get(\’database.ssl\’, False) # False(默认值)
# 保存到文件
config.save()
# 加载已存在的配置
config2 = ConfigManager(\’app.json\’)
print(config2.get(\’database.port\’)) # 3306
💡 编程思路
这道题考察 JSON 读写 + 嵌套字典操作 + 异常处理:
🖥️ 参考代码
import json
from pathlib import Path
class ConfigError(Exception):
\”\”\”配置相关错误\”\”\”
pass
class ConfigManager:
\”\”\”JSON 配置文件管理器\”\”\”
def __init__(self, config_path):
self.config_path = Path(config_path)
self._config = {
}
self._load()
def _load(self):
\”\”\”从文件加载配置\”\”\”
try:
if self.config_path.exists():
with open(self.config_path, \’r\’, encoding=\’utf-8\’) as f:
self._config = json.load(f)
else:
self._config = {
}
except json.JSONDecodeError as e:
raise ConfigError(f\”配置文件格式错误: {
e}\”) from e
except PermissionError as e:
raise ConfigError(f\”无法读取配置文件: {
e}\”) from e
def get(self, key, default=None):
\”\”\”
获取配置值
参数:
key: 点号分隔的键路径,如 \’database.host\’
default: 键不存在时返回的默认值
\”\”\”
keys = key.split(\’.\’)
value = self._config
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def set(self, key, value):
\”\”\”
设置配置值
参数:
key: 点号分隔的键路径
value: 要设置的值
\”\”\”
keys = key.split(\’.\’)
config = self._config
# 创建中间层级
for k in keys[:–1]:
if k not in config:
config[k] = {
}
elif not isinstance(config[k], dict):
raise ConfigError(f\”无法设置 \’{
key}\’: \’{
k}\’ 不是字典\”)
config = config[k]
config[keys[–1]] = value
def delete(self, key):
\”\”\”删除配置项\”\”\”
keys = key.split(\’.\’)
config = self._config
for k in keys[:–1]:
if k not in config or not isinstance(config[k], dict):
return False
config = config[k]
if keys[–1] in config:
del config[keys[–1]]
return True
return False
def save(self, indent=2):
\”\”\”保存配置到文件\”\”\”
try:
self.config_path.parent.mkdir(parents=True, exist_ok=True)
with open(self.config_path, \’w\’, encoding=\’utf-8\’) as f:
json.dump(self._config, f, indent=indent, ensure_ascii=False)
except PermissionError as e:
raise ConfigError(f\”无法写入配置文件: {
e}\”) from e
def reload(self):
\”\”\”重新加载配置文件\”\”\”
self._load()
def to_dict(self)




