跳转至

04 - 文件操作与异常处理

学习时间: 45-60分钟 重要性: ⭐⭐⭐⭐⭐ 实际开发必备


🎯 学习目标

  • 掌握文件的读写操作
  • 理解上下文管理器(with语句)
  • 学会正确处理异常
  • 了解路径操作(pathlib)

📁 文件读写

基本读写操作

Python
# 读取文件
# 方法1: 一次性读取全部(小文件)
with open("example.txt", "r", encoding="utf-8") as f:  # with语句自动管理文件关闭,即使出错也保证资源释放
    content = f.read()
    print(content)

# 方法2: 逐行读取(大文件)
with open("example.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())  # strip()去除换行符

# 方法3: 读取所有行到列表
with open("example.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()
    print(lines)  # ['line1\n', 'line2\n', 'line3\n']

# 写入文件
# 覆盖写入
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello, World!\n")
    f.write("第二行\n")

# 追加写入
with open("output.txt", "a", encoding="utf-8") as f:
    f.write("这是新的一行\n")

# 写入多行
lines = ["第一行\n", "第二行\n", "第三行\n"]
with open("output.txt", "w", encoding="utf-8") as f:
    f.writelines(lines)

文件模式

Python
# 模式列表
"""
'r'  - 只读(默认)
'w'  - 写入(会覆盖已存在的文件)
'a'  - 追加(在文件末尾写入)
'x'  - 独占创建(文件存在则失败)
'b'  - 二进制模式
't'  - 文本模式(默认)
'+'  - 更新模式(读写)
"""

# 组合模式
# 'rb' - 读取二进制(图片、视频等)
with open("image.jpg", "rb") as f:
    image_data = f.read()

# 'wb' - 写入二进制
with open("output.jpg", "wb") as f:
    f.write(image_data)

# 'r+' - 读写(文件必须存在)
with open("data.txt", "r+", encoding="utf-8") as f:
    content = f.read()
    f.write("追加的内容")

with语句的重要性

Python
# ❌ 不好:可能忘记关闭文件
f = open("file.txt", "r")
content = f.read()
# 如果这里出现异常,文件不会关闭
f.close()

# ✅ 好:使用with语句,自动关闭
with open("file.txt", "r") as f:
    content = f.read()
# 无论是否异常,文件都会自动关闭

# 等价于
f = open("file.txt", "r")
try:
    content = f.read()
finally:
    f.close()

📂 pathlib - 现代化的路径操作

基本使用

Python
from pathlib import Path

# 创建路径对象
path = Path("data/example.txt")

# 路径拼接(推荐)
path = Path("data") / "example.txt"
# 或
path = Path("data").joinpath("example.txt")

# 路径信息
print(path.name)         # "example.txt" 文件名
print(path.stem)         # "example" 不含扩展名
print(path.suffix)       # ".txt" 扩展名
print(path.parent)       # "data" 父目录
print(path.exists())     # True/False 是否存在
print(path.is_file())    # True/False 是否是文件
print(path.is_dir())     # True/False 是否是目录

# 读写文件
content = path.read_text(encoding="utf-8")  # 读取文本
path.write_text("Hello, World!", encoding="utf-8")  # 写入文本

data = path.read_bytes()  # 读取二进制
path.write_bytes(b"binary data")  # 写入二进制

目录操作

Python
from pathlib import Path

# 创建目录
Path("data/subdir").mkdir(parents=True, exist_ok=True)
# parents=True: 创建父目录
# exist_ok=True: 目录存在不报错

# 遍历目录
path = Path("data")
for item in path.iterdir():
    if item.is_file():
        print(f"文件: {item.name}")
    elif item.is_dir():
        print(f"目录: {item.name}")

# 递归遍历
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# 只查找当前目录
for txt_file in Path(".").glob("*.txt"):
    print(txt_file)

实用示例

Python
from pathlib import Path

# 重命名文件
Path("old.txt").rename("new.txt")

# 移动文件
Path("data.txt").rename("backup/data.txt")

# 删除文件
Path("temp.txt").unlink()

# 删除目录(必须是空目录)
Path("temp_dir").rmdir()

# 获取绝对路径
path = Path("example.txt")
print(path.absolute())

# 获取相对路径
path = Path("data/example.txt")
print(path.relative_to("project"))

# 检查文件扩展名
if path.suffix in [".jpg", ".png", ".gif"]:
    print("这是图片文件")

⚠️ 异常处理

基本语法

Python
# 基本结构
try:
    # 可能出错的代码
    result = 10 / 0
except ZeroDivisionError as e:
    # 捕获特定异常
    print(f"错误: {e}")
except ValueError as e:
    # 捕获另一种异常
    print(f"值错误: {e}")
else:
    # 没有异常时执行
    print(f"结果: {result}")
finally:
    # 无论是否异常都执行
    print("清理工作")

# 捕获多个异常
try:
    number = int(input("输入数字: "))
    result = 100 / number
except (ValueError, ZeroDivisionError) as e:
    print(f"错误: {e}")

常见异常类型

Python
# 1. FileNotFoundError
try:
    with open("不存在的文件.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    print("文件不存在")

# 2. ValueError
try:
    number = int("abc")
except ValueError:
    print("无法转换为整数")

# 3. TypeError
try:
    result = "10" + 5  # 字符串+数字
except TypeError:
    print("类型错误")

# 4. KeyError (字典)
person = {"name": "张三"}
try:
    print(person["age"])  # 键不存在
except KeyError:
    print("键不存在")

# 更好的方式:使用get()
age = person.get("age", "未知")  # 不会报错

# 5. IndexError (列表)
numbers = [1, 2, 3]
try:
    print(numbers[10])  # 索引超出范围
except IndexError:
    print("索引超出范围")

# 6. AttributeError
class Person:
    pass

person = Person()
try:
    print(person.name)  # 属性不存在
except AttributeError:
    print("属性不存在")

最佳实践

Python
# 1. 明确指定异常类型(不要用裸except)
# ❌ 不好
try:
    result = 10 / 0
except:  # 捕获所有异常,包括SystemExit等
    pass

# ✅ 好
try:
    result = 10 / 0
except ZeroDivisionError:
    pass

# 2. 提供有用的错误信息
try:
    with open(filepath, "r") as f:
        data = json.load(f)  # json.load从文件读取JSON数据;json.dump将对象写入文件为JSON
    except FileNotFoundError:
    logging.error(f"配置文件不存在: {filepath}")
    raise  # 重新抛出异常
except json.JSONDecodeError as e:
    logging.error(f"配置文件格式错误: {filepath}")
    raise ValueError(f"Invalid JSON in {filepath}") from e

# 3. 使用finally进行清理
def process_file(filepath):
    f = None
    try:
        f = open(filepath, "r")
        # 处理文件
        return f.read()
    except IOError as e:
        print(f"文件错误: {e}")
    finally:
        if f:
            f.close()  # 确保文件关闭

💡 实用模式

安全的文件读取

Python
def read_file_safely(filepath):
    """
    安全地读取文件,处理各种异常
    """
    try:
        return Path(filepath).read_text(encoding="utf-8")
    except FileNotFoundError:
        print(f"错误: 文件不存在 - {filepath}")
    except PermissionError:
        print(f"错误: 没有权限读取文件 - {filepath}")
    except UnicodeDecodeError:
        print(f"错误: 文件编码不是UTF-8 - {filepath}")
    except Exception as e:
        print(f"未知错误: {e}")
    return None

# 使用
content = read_file_safely("example.txt")
if content:
    print(content)

JSON文件处理

Python
import json
from pathlib import Path

# 读取JSON
def load_json(filepath):
    try:
        content = Path(filepath).read_text(encoding="utf-8")
        return json.loads(content)
    except FileNotFoundError:
        print(f"文件不存在: {filepath}")
    except json.JSONDecodeError as e:
        print(f"JSON格式错误: {e}")
    return None

# 写入JSON(格式化)
def save_json(data, filepath):
    try:
        content = json.dumps(data, ensure_ascii=False, indent=2)
        Path(filepath).write_text(content, encoding="utf-8")
        return True
    except Exception as e:
        print(f"保存失败: {e}")
        return False

# 使用
data = {
    "name": "张三",
    "age": 25,
    "scores": [85, 90, 78]
}

save_json(data, "data.json")
loaded_data = load_json("data.json")

CSV文件处理(基础)

Python
import csv
from pathlib import Path

# 读取CSV
def read_csv(filepath):
    data = []
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            reader = csv.DictReader(f)  # 使用字典读取
            for row in reader:
                data.append(row)
        return data
    except Exception as e:
        print(f"读取CSV失败: {e}")
        return None

# 写入CSV
def write_csv(data, filepath):
    try:
        with open(filepath, "w", encoding="utf-8", newline="") as f:
            if data:
                writer = csv.DictWriter(f, fieldnames=data[0].keys())
                writer.writeheader()
                writer.writerows(data)
        return True
    except Exception as e:
        print(f"写入CSV失败: {e}")
        return False

# 使用
data = [
    {"name": "张三", "age": 25, "city": "北京"},
    {"name": "李四", "age": 30, "city": "上海"}
]

write_csv(data, "output.csv")
loaded_data = read_csv("output.csv")

配置文件处理

Python
import json
from pathlib import Path

class Config:
    """简单的配置管理器"""

    def __init__(self, filepath="config.json"):
        self.filepath = Path(filepath)
        self.config = {}
        self.load()

    def load(self):
        """加载配置"""
        if self.filepath.exists():
            try:
                content = self.filepath.read_text(encoding="utf-8")
                self.config = json.loads(content)
            except Exception as e:
                print(f"加载配置失败: {e}")
                self.config = {}
        else:
            # 创建默认配置
            self.config = self.get_default_config()
            self.save()

    def save(self):
        """保存配置"""
        try:
            content = json.dumps(self.config, ensure_ascii=False, indent=2)
            self.filepath.write_text(content, encoding="utf-8")
            return True
        except Exception as e:
            print(f"保存配置失败: {e}")
            return False

    def get(self, key, default=None):
        """获取配置项"""
        return self.config.get(key, default)

    def set(self, key, value):
        """设置配置项"""
        self.config[key] = value
        return self.save()

    @staticmethod
    def get_default_config():
        return {
            "debug": False,
            "max_retries": 3,
            "timeout": 30
        }

# 使用
config = Config()
debug_mode = config.get("debug", False)
config.set("max_retries", 5)

📝 练习

练习1: 文件词频统计

Python
def word_frequency(filepath):
    """
    读取文本文件,统计每个词出现的频率
    返回字典:{单词: 频率}
    """
    # TODO: 实现这个函数
    # 提示:
    # 1. 读取文件
    # 2. 分词(split())
    # 3. 统计频率(使用字典)
    pass

# 测试
result = word_frequency("example.txt")
print(result)

练习2: 批量处理文件

Python
from pathlib import Path

def batch_rename(directory, pattern, replacement):
    """
    批量重命名目录中的文件
    将文件名中的pattern替换为replacement

    例如: batch_rename("data/", "old", "new")
    将 file_old_1.txt -> file_new_1.txt
    """
    # TODO: 实现这个函数
    pass

练习3: 异常安全的配置加载

Python
def load_config_with_defaults(filepath, defaults):
    """
    加载配置文件,如果加载失败或缺少某些键,
    使用默认值填充

    filepath: 配置文件路径(JSON格式)
    defaults: 默认配置字典
    """
    # TODO: 实现这个函数
    pass

🎯 自我检查

完成这个主题后,你应该:

  • 能使用with语句读写文件
  • 理解pathlib的基本使用
  • 能正确处理常见异常
  • 知道如何处理JSON和CSV文件
  • 能写出异常安全的代码
  • 不查资料完成上面的练习

📚 延伸阅读


下一步: 05 - 装饰器与生成器