← 返回 Python 7天入门专题

Day 6 · 文件 / 异常 / 模块

文件读写 · with语句 · try/except · import · 常用标准库 | 建议时长:3-4小时
第一性原理:程序到目前为止都是"自包含"的——数据写死在代码里,出错就崩溃,只能用自己写的代码。今天解决三个问题:①数据持久化(读写文件)、②优雅处理错误(异常处理)、③复用他人代码(模块导入)。

1. 文件读写 — 数据持久化

第一性原理:程序关闭后内存中的数据就没了。要让数据"活下来",必须写入磁盘文件。文件操作本质就是:打开文件 → 读/写 → 关闭文件。

1.1 基本文件操作

# 写入文件
f = open("test.txt", "w", encoding="utf-8")
f.write("第一行\n")
f.write("第二行\n")
f.close()    # ⚠️ 必须关闭!否则数据可能没写入

# 读取文件
f = open("test.txt", "r", encoding="utf-8")
content = f.read()    # 读取全部内容
print(content)
f.close()
模式含义文件不存在时
"r"只读(默认)报错
"w"只写(覆盖)创建
"a"追加创建
"r+"读写报错
"w+"读写(覆盖)创建
"a+"读写(追加)创建
⚠️ 两个必记事项:
  1. 永远加 encoding="utf-8" — 否则Windows默认GBK编码,中文容易乱码
  2. 永远要 close() — 不关闭文件,数据可能没真正写入磁盘,且会占用系统资源

1.2 with语句 — 自动关闭文件(推荐写法)

# 用with语句,代码块结束后自动关闭文件,即使出错也会关闭
with open("test.txt", "w", encoding="utf-8") as f:
    f.write("用with语句\n")
    f.write("自动关闭\n")
# 离开with块后f自动关闭

# 读取也一样
with open("test.txt", "r", encoding="utf-8") as f:
    content = f.read()
永远用with语句操作文件!不用手动close,不会忘记关闭,即使中间报错也会正确关闭。这是Python文件操作的最佳实践。

1.3 读取文件的多种方式

# 1. read() — 一次读全部
with open("test.txt", encoding="utf-8") as f:
    content = f.read()

# 2. readline() — 每次读一行
with open("test.txt", encoding="utf-8") as f:
    line1 = f.readline()
    line2 = f.readline()

# 3. readlines() — 读所有行,返回列表
with open("test.txt", encoding="utf-8") as f:
    lines = f.readlines()  # ['第一行\n', '第二行\n', ...]

# 4. 直接遍历文件对象(最推荐!内存友好)
with open("test.txt", encoding="utf-8") as f:
    for line in f:          # 一行一行读,不占内存
        print(line.strip())  # strip()去掉行尾换行符

1.4 写入文件

# write() — 写入字符串
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello\n")
    f.write("World\n")

# writelines() — 写入字符串列表(不加换行)
lines = ["第一行\n", "第二行\n", "第三行\n"]
with open("output.txt", "w", encoding="utf-8") as f:
    f.writelines(lines)

# print() 直接写文件
with open("output.txt", "w", encoding="utf-8") as f:
    print("Hello", file=f)
    print("World", file=f)

1.5 文件路径

import os

# 当前工作目录
print(os.getcwd())

# 拼接路径(不要用字符串拼接!)
path = os.path.join("folder", "subfolder", "file.txt")
# Windows: folder\subfolder\file.txt
# Mac/Linux: folder/subfolder/file.txt

# 检查文件是否存在
print(os.path.exists("test.txt"))    # True/False

# 检查是文件还是目录
print(os.path.isfile("test.txt"))    # True
print(os.path.isdir("folder"))      # True/False

# 创建目录
os.makedirs("new_folder/sub", exist_ok=True)

# 列出目录内容
files = os.listdir(".")    # 当前目录所有文件

# 推荐用pathlib(更现代)
from pathlib import Path

p = Path("folder") / "file.txt"
p.exists()
p.read_text(encoding="utf-8")       # 一行读文件
p.write_text("内容", encoding="utf-8")  # 一行写文件

1.6 JSON文件(最常用的数据交换格式)

import json

# Python数据 → JSON文件
data = {
    "name": "张三",
    "age": 20,
    "courses": ["数学", "Python"],
    "active": True
}

with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)
    # ensure_ascii=False 让中文正常显示
    # indent=2 让格式美观

# JSON文件 → Python数据
with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

# JSON字符串 ↔ Python字典
json_str = json.dumps(data, ensure_ascii=False)  # 字典→字符串
data2 = json.loads(json_str)                     # 字符串→字典

2. 异常处理 — 优雅地处理错误

第一性原理:程序运行时会遇到各种意外——文件不存在、除以零、网络断开。如果不处理,程序直接崩溃。异常处理就是"预判可能出错的地方,出错时优雅应对"而不是崩溃。

2.1 try-except 基本结构

# 不处理异常 → 程序崩溃
# print(10 / 0)  ← ZeroDivisionError,程序崩溃

# 处理异常 → 优雅应对
try:
    result = 10 / 0
except ZeroDivisionError:
    print("不能除以零!")
# 程序继续运行

2.2 完整结构

try:
    # 可能出错的代码
    num = int(input("输入数字: "))
    result = 100 / num
    print(f"结果: {result}")

except ValueError as e:
    # 捕获特定异常,e是异常对象
    print(f"输入不是数字: {e}")

except ZeroDivisionError:
    # 捕获另一个异常
    print("不能除以零")

except Exception as e:
    # 捕获所有其他异常(兜底)
    print(f"未知错误: {e}")

else:
    # try块没有异常时执行
    print("计算成功!")

finally:
    # 不管有没有异常都执行(清理工作)
    print("程序结束")
关键字作用必须?
try包裹可能出错的代码是
except捕获并处理异常是
else没异常时执行否
finally无论如何都执行否

2.3 常见异常类型

异常触发场景
ValueError值错误(如int("abc"))
TypeError类型错误(如"abc" + 1)
ZeroDivisionError除以零
IndexError列表索引越界
KeyError字典键不存在
FileNotFoundError文件不存在
AttributeError属性/方法不存在
NameError变量名未定义
ImportError导入模块失败

2.4 主动抛出异常

def set_age(age):
    if age < 0 or age > 150:
        raise ValueError(f"年龄必须在0-150之间,你输入了{age}")
    return age

try:
    set_age(-5)
except ValueError as e:
    print(e)  # 年龄必须在0-150之间,你输入了-5

2.5 自定义异常

# 自定义异常类(继承Exception)
class InsufficientFunds(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"余额{balance}不足,无法取{amount}")

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFunds(balance, amount)
    return balance - amount

try:
    withdraw(100, 200)
except InsufficientFunds as e:
    print(e)         # 余额100不足,无法取200
    print(e.balance)  # 100

3. 模块与包 — 代码复用

第一性原理:把所有代码写在一个文件里会变成几千行的怪物。模块就是把代码分成多个文件,每个文件是一个模块,可以互相导入。包是模块的目录。

3.1 导入模块的几种方式

# 方式1:import 模块名
import math
print(math.pi)          # 3.14159...
print(math.sqrt(16))    # 4.0

# 方式2:import 模块名 as 别名
import numpy as np      # 常用别名
import pandas as pd

# 方式3:from 模块名 import 具体内容
from math import pi, sqrt
print(pi)               # 不用math.前缀
print(sqrt(16))

# 方式4:from 模块名 import *(不推荐!污染命名空间)
# from math import *

# 方式5:导入自定义模块
# 假设有 utils.py 文件,里面有函数 helper()
# from utils import helper

3.2 __name__ == "__main__"

# 在 utils.py 中
def helper():
    print("工具函数")

# 这行的作用:只有直接运行此文件时才执行
# 被import时不执行
if __name__ == "__main__":
    print("直接运行utils.py")
    helper()

3.3 常用标准库速查

模块用途常用功能
os操作系统接口文件/目录操作、环境变量
sys系统相关sys.argv命令行参数、sys.exit()
jsonJSON处理json.load/dump/loads/dumps
datetime日期时间date.today()、datetime.now()
random随机数random.randint/choice/shuffle
math数学函数math.pi/sqrt/ceil/floor
re正则表达式re.match/search/findall/sub
collections扩展数据结构Counter/defaultdict/deque
pathlib路径处理(现代)Path对象
itertools迭代工具chain/product/combinations

3.4 标准库实战示例

# === datetime ===
from datetime import datetime, date, timedelta

now = datetime.now()
print(now)                       # 2026-08-02 14:30:00.123456
print(now.strftime("%Y-%m-%d %H:%M"))  # 2026-08-02 14:30

today = date.today()
tomorrow = today + timedelta(days=1)
print(tomorrow)

# 字符串 → 日期
dt = datetime.strptime("2026-01-15", "%Y-%m-%d")

# === random ===
import random

print(random.randint(1, 100))      # 1-100随机整数
print(random.choice(["A", "B", "C"]))  # 随机选一个
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)               # 打乱顺序
print(random.sample(range(1, 50), 6))  # 随机选6个不重复

# === collections.Counter ===
from collections import Counter

text = "the cat sat on the mat"
word_count = Counter(text.split())
print(word_count)           # Counter({'the': 2, 'cat': 1, ...})
print(word_count.most_common(2))  # [('the', 2), ('cat', 1)]

# === collections.defaultdict ===
from collections import defaultdict

# 自动初始化默认值
word_group = defaultdict(list)
words = [("fruit", "apple"), ("fruit", "banana"), ("veg", "carrot")]
for category, word in words:
    word_group[category].append(word)
print(dict(word_group))  # {'fruit': ['apple', 'banana'], 'veg': ['carrot']}

# === re 正则表达式 ===
import re

# 查找所有邮箱
text = "联系: alice@qq.com 或 bob@gmail.com"
emails = re.findall(rr'\S+@\S+\.\S+', text)
print(emails)  # ['alice@qq.com', 'bob@gmail.com']

# 替换
result = re.sub(rr'\d+', "#", "电话13800001234,邮编100000")
print(result)  # 电话#,邮编#

4. 综合实战:通讯录程序(文件持久化 + 异常处理)

import json
import os

CONTACTS_FILE = "contacts.json"

def load_contacts():
    """从文件加载通讯录"""
    if not os.path.exists(CONTACTS_FILE):
        return {}
    try:
        with open(CONTACTS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except json.JSONDecodeError:
        print("通讯录文件损坏,重新创建")
        return {}

def save_contacts(contacts):
    """保存通讯录到文件"""
    with open(CONTACTS_FILE, "w", encoding="utf-8") as f:
        json.dump(contacts, f, ensure_ascii=False, indent=2)

def main():
    contacts = load_contacts()

    while True:
        print("\n===== 通讯录 =====")
        print("1.添加 2.查找 3.删除 4.全部 5.退出")
        choice = input("选择: ").strip()

        if choice == "1":
            name = input("姓名: ").strip()
            if not name:
                print("姓名不能为空")
                continue
            phone = input("电话: ").strip()
            contacts[name] = phone
            save_contacts(contacts)
            print(f"已添加 {name}")

        elif choice == "2":
            name = input("查找姓名: ").strip()
            if name in contacts:
                print(f"{name}: {contacts[name]}")
            else:
                print("未找到")

        elif choice == "3":
            name = input("删除姓名: ").strip()
            if name in contacts:
                del contacts[name]
                save_contacts(contacts)
                print("已删除")
            else:
                print("未找到")

        elif choice == "4":
            if not contacts:
                print("通讯录为空")
            else:
                for name, phone in sorted(contacts.items()):
                    print(f"  {name}: {phone}")

        elif choice == "5":
            print("再见!")
            break
        else:
            print("无效选择")

if __name__ == "__main__":
    main()

5. 练习题(独立完成)

练习1:日志记录器

写一个函数 logger(message, level="INFO"),把消息写入 log.txt,格式为 [时间] [级别] 消息。用datetime获取时间,用with语句写文件(追加模式)。

参考答案
from datetime import datetime

def logger(message, level="INFO"):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{timestamp}] [{level}] {message}\n"
    with open("log.txt", "a", encoding="utf-8") as f:
        f.write(line)
    print(line.strip())

logger("程序启动")
logger("文件未找到", "WARNING")
logger("数据库连接失败", "ERROR")

练习2:安全计算器

用try-except写一个计算器函数,处理:除以零、输入非数字、未知错误。每种错误给出友好的中文提示。

参考答案
def safe_calculate(a_str, op, b_str):
    try:
        a = float(a_str)
        b = float(b_str)
        
        if op == "+": return a + b
        elif op == "-": return a - b
        elif op == "*": return a * b
        elif op == "/": return a / b
        else: return f"不支持运算符: {op}"
        
    except ValueError:
        return "错误:请输入有效数字"
    except ZeroDivisionError:
        return "错误:不能除以零"
    except Exception as e:
        return f"未知错误:{e}"

print(safe_calculate("10", "+", "5"))    # 15.0
print(safe_calculate("10", "/", "0"))    # 错误:不能除以零
print(safe_calculate("abc", "+", "5"))   # 错误:请输入有效数字

练习3:成绩文件管理

写程序:①输入5个学生姓名和成绩,保存到JSON文件;②读取JSON文件,输出平均分和最高分学生。

参考答案
import json

# 写入
students = []
for i in range(5):
    name = input(f"学生{i+1}姓名: ")
    score = float(input(f"{name}成绩: "))
    students.append({"name": name, "score": score})

with open("grades.json", "w", encoding="utf-8") as f:
    json.dump(students, f, ensure_ascii=False, indent=2)

# 读取
with open("grades.json", "r", encoding="utf-8") as f:
    data = json.load(f)

avg = sum(s["score"] for s in data) / len(data)
best = max(data, key=lambda s: s["score"])
print(f"平均分: {avg:.1f}")
print(f"最高分: {best['name']} ({best['score']})")

练习4:文件分析器

读取一个文本文件,统计:总行数、总单词数、总字符数、出现最多的5个单词。用Counter。

参考答案
from collections import Counter

def analyze_file(filepath):
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            content = f.read()
        
        lines = content.splitlines()
        words = content.split()
        chars = len(content)
        
        word_freq = Counter(word.lower().strip(".,!?") for word in words)
        
        print(f"总行数: {len(lines)}")
        print(f"总单词数: {len(words)}")
        print(f"总字符数: {chars}")
        print(f"最常见5个单词:")
        for word, count in word_freq.most_common(5):
            print(f"  {word}: {count}次")
            
    except FileNotFoundError:
        print(f"文件 {filepath} 不存在")

analyze_file("test.txt")

6. Day 6 小结

今天你学会了什么