悟夕导航

🐍 Python 第三课:从白银到黄金——文件操作、模块与面向对象

14 0 0

课程目标:

  • 学会读写文件,让数据不再"随风而逝"
  • 掌握模块和包,告别"把所有代码写在一个文件里"的野蛮时代
  • 初探面向对象编程(OOP),理解"类"和"对象"这对好基友
  • 认识常用标准库,站在 Python 的肩膀上
  • 完成一个实战小项目,把所学知识串起来

1) 回顾上节课

上节课我们学会了:

  • 条件判断(if/elif/else):让程序有脑子
  • 循环(while/for):让程序有毅力
  • 函数:代码的打包艺术
  • 数据结构:列表、字典、元组、集合
  • 错误处理:try/except/finally

常见问题解答:

Q:为什么我的列表修改后,另一个变量也变了?

a = [1, 2, 3]
b = a
b.append(4)
print(a)  # [1, 2, 3, 4]  WTF?!

A:因为 b = a 不是复制,而是给同一个列表起了两个名字!要复制用 b = a.copy()b = list(a)

Q:函数里修改传入的列表,外面的列表也会变?
A:是的!因为传的是引用(地址)。如果不想影响原列表,先在函数里 copy() 一份。


2) 文件操作:让数据"永生"

程序关闭后,变量里的数据就消失了。要持久保存,得写入文件。

读取文件

# 方法1:传统方式(记得手动关闭!)
f = open("story.txt", "r", encoding="utf-8")
content = f.read()  # 读取全部
print(content)
f.close()  # 别忘了关!否则资源泄露

# 方法2:with 语句(推荐!自动关闭)
with open("story.txt", "r", encoding="utf-8") as f:
    content = f.read()
    # 出了这个缩进,文件自动关闭,就算出错也会关

打开模式:

模式说明
"r"只读(默认),文件不存在报错
"w"只写,会清空原有内容! 文件不存在则创建
"a"追加,在末尾添加,不清空
"r+"读写
"b"二进制模式(如 "rb""wb"),用于图片、视频等

各种读取方法

with open("data.txt", "r", encoding="utf-8") as f:
    # 方法1:read() 读取全部,大文件会卡死
    all_content = f.read()
    
    # 方法2:readline() 读一行
    first_line = f.readline()
    
    # 方法3:readlines() 读取为列表,每行一个元素
    lines = f.readlines()
    
    # 方法4:直接遍历(最推荐,省内存)
    for line in f:
        print(line.strip())  # strip() 去掉行末的换行符

写入文件

# 写入会覆盖原内容!
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello, World!\n")
    f.write("第二行\n")
    
    lines = ["第一行\n", "第二行\n", "第三行\n"]
    f.writelines(lines)  # 写入多行,注意要自己加换行符

追加模式:

with open("log.txt", "a", encoding="utf-8") as f:
    f.write("2024-01-01: 用户登录\n")  # 在文件末尾添加,不覆盖

文件和目录操作(os 模块)

import os

# 文件操作
os.rename("old.txt", "new.txt")      # 重命名
os.remove("trash.txt")               # 删除文件
os.path.exists("file.txt")           # 判断是否存在
os.path.getsize("file.txt")          # 获取文件大小(字节)

# 目录操作
os.mkdir("newfolder")                # 创建目录
os.rmdir("empty_folder")             # 删除空目录
os.listdir(".")                      # 列出目录内容
os.getcwd()                          # 获取当前工作目录
os.chdir("/path/to/dir")             # 切换目录

# 路径拼接(跨平台!Windows 用 \,Linux/Mac 用 /)
path = os.path.join("folder", "subfolder", "file.txt")
# 结果:Windows 下是 "folder\subfolder\file.txt"
#       Linux/Mac 下是 "folder/subfolder/file.txt"

JSON 数据处理

JSON 是网络数据交换的标准格式,Python 有内置支持:

import json

data = {
    "name": "张三",
    "age": 25,
    "hobbies": ["编程", "游戏", "睡觉"],
    "is_student": False
}

# Python 对象 → JSON 字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)

# 保存到文件
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# 从 JSON 字符串 → Python 对象
parsed = json.loads(json_str)

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

注意: ensure_ascii=False 让中文正常显示,不然会变成 \u4e2d\u6587


3) 模块和包:代码的"乐高积木"

什么是模块?

一个 .py 文件就是一个模块。你可以把功能相关的代码放在一个文件里,其他地方导入使用。

文件结构:

myproject/
├── main.py          # 主程序
├── utils.py         # 工具函数模块
└── data/
    └── students.json

utils.py 内容:

# 工具函数模块

def add(a, b):
    """加法"""
    return a + b

def multiply(a, b):
    """乘法"""
    return a * b

PI = 3.14159  # 模块级别的变量

main.py 中使用:

# 导入整个模块
import utils

print(utils.add(2, 3))
print(utils.PI)

# 导入特定函数
from utils import add, multiply
print(add(2, 3))  # 可以直接用,不用加 utils. 前缀

# 导入并起别名(名字太长时有用)
import utils as u
print(u.multiply(4, 5))

# 导入所有(不推荐,容易命名冲突)
from utils import *

包(Package):模块的文件夹

当项目变大,需要把模块组织成文件夹:

myproject/
├── main.py
├── math_tools/
│   ├── __init__.py      # 必须有这个文件,哪怕为空
│   ├── basic.py         # 基础运算
│   └── advanced.py      # 高级运算
└── utils/
    ├── __init__.py
    └── helpers.py

__init__.py 的作用:

  • Python 3.3+ 之前,必须有这个文件才算包
  • 现在可以没有,但建议保留,用来控制导入行为

导入包中的模块:

# 方式1
from math_tools import basic
print(basic.add(1, 2))

# 方式2
from math_tools.basic import add
print(add(1, 2))

# 方式3
import math_tools.basic as mb
print(mb.add(1, 2))

标准库和第三方库

常用标准库(自带,无需安装):

模块用途
os操作系统接口
sys系统相关
datetime日期时间
jsonJSON 处理
re正则表达式
random随机数
math / statistics数学运算
collections高级数据结构
itertools迭代器工具

第三方库(需要安装):

# 使用 pip 安装
pip install requests      # HTTP 请求
pip install numpy         # 科学计算
pip install pandas        # 数据分析
pip install matplotlib    # 绘图
pip install flask         # Web 框架

使用虚拟环境(重要!):

# 创建虚拟环境
python -m venv myenv

# 激活(Windows)
myenv\Scripts\activate

# 激活(Mac/Linux)
source myenv/bin/activate

# 退出
deactivate

为什么用虚拟环境?

  • 不同项目依赖不同版本的库
  • 避免污染全局 Python 环境
  • 方便打包和部署

4) 面向对象编程(OOP):从"过程"到"对象"

为什么需要 OOP?

面向过程: 把问题拆成步骤,按顺序执行。

# 面向过程写法
def create_student(name, age):
    return {"name": name, "age": age}

def student_say_hello(student):
    print(f"你好,我是{student['name']}")

s1 = create_student("张三", 20)
student_say_hello(s1)

面向对象: 把数据和操作数据的方法打包成"对象"。

# 面向对象写法
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def say_hello(self):
        print(f"你好,我是{self.name}")

s1 = Student("张三", 20)
s1.say_hello()

好处: 代码更有组织性,易于扩展和维护,适合大型项目。

类和对象的基本概念

  • 类(Class):对象的"设计图纸",定义了对象有什么属性、能做什么
  • 对象(Object):根据类创建的"实例",是具体的存在
  • 属性(Attribute):对象的数据(姓名、年龄等)
  • 方法(Method):对象的行为(打招呼、学习等)

类比:

  • 类 = 汽车设计图纸
  • 对象 = 根据图纸造出来的一辆辆具体汽车
  • 属性 = 颜色、品牌、排量
  • 方法 = 加速、刹车、转弯

定义类和创建对象

class Dog:
    """狗狗类"""
    
    # 类属性:所有实例共享
    species = "Canis familiaris"
    
    def __init__(self, name, age):
        """构造方法:创建对象时自动调用"""
        self.name = name      # 实例属性
        self.age = age
    
    def bark(self):
        """实例方法"""
        print(f"{self.name}说:汪汪!")
    
    def get_info(self):
        return f"{self.name}今年{self.age}岁"

# 创建对象(实例化)
my_dog = Dog("旺财", 3)
your_dog = Dog("来福", 5)

# 访问属性和方法
print(my_dog.name)      # 旺财
my_dog.bark()           # 旺财说:汪汪!
print(Dog.species)      # Canis familiaris(通过类访问)
print(my_dog.species)   # Canis familiaris(通过实例访问)

关键概念:

  • __init__:构造方法,创建对象时自动执行,self 代表当前对象
  • self:必须作为第一个参数,但调用时不用传,Python 自动处理
  • 实例属性(self.xxx):每个对象独立
  • 类属性(直接定义在类里):所有对象共享

继承:站在巨人的肩膀上

class Animal:
    """动物基类"""
    
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError("子类必须实现这个方法")

class Cat(Animal):  # Cat 继承 Animal
    """猫咪类"""
    
    def speak(self):  # 重写父类方法
        print(f"{self.name}说:喵喵~")

class Dog(Animal):
    """狗狗类"""
    
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类构造方法
        self.breed = breed      # 新增属性
    
    def speak(self):
        print(f"{self.name}说:汪汪!")
    
    def fetch(self):
        """狗狗特有的方法"""
        print(f"{self.name}去捡球了")

# 使用
cat = Cat("咪咪")
dog = Dog("旺财", "金毛")

cat.speak()  # 咪咪说:喵喵~
dog.speak()  # 旺财说:汪汪!
dog.fetch()  # 旺财去捡球了

继承的好处:

  • 代码复用:共同的代码写在父类
  • 扩展性:子类可以新增属性和方法
  • 多态:不同子类对同一方法有不同实现

封装:保护数据

class BankAccount:
    """银行账户"""
    
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = balance  # 双下划线:私有属性,外部无法直接访问
    
    def deposit(self, amount):
        """存款"""
        if amount > 0:
            self.__balance += amount
            print(f"存入{amount}元,当前余额:{self.__balance}")
        else:
            print("存款金额必须大于0")
    
    def withdraw(self, amount):
        """取款"""
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"取出{amount}元,当前余额:{self.__balance}")
            return amount
        else:
            print("余额不足或金额无效")
            return 0
    
    def get_balance(self):
        """获取余额(只读)"""
        return self.__balance

account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance())    # 1300
# print(account.__balance)      # 报错!AttributeError

命名约定:

  • __xxx:双下划线,私有属性/方法,外部无法访问( name mangling 机制)
  • _xxx:单下划线,约定俗成的"内部使用",但技术上可以访问
  • xxx:公有,随意访问

特殊方法(魔术方法)

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
    
    def __str__(self):
        """print() 时调用"""
        return f"《{self.title}》by {self.author}"
    
    def __repr__(self):
        """交互式环境直接显示对象时调用"""
        return f"Book('{self.title}', '{self.author}', {self.pages})"
    
    def __len__(self):
        """len() 时调用"""
        return self.pages
    
    def __eq__(self, other):
        """== 比较时调用"""
        if not isinstance(other, Book):
            return False
        return self.title == other.title and self.author == other.author

book = Book("三体", "刘慈欣", 300)
print(book)           # 《三体》by 刘慈欣
print(repr(book))     # Book('三体', '刘慈欣', 300)
print(len(book))      # 300

book2 = Book("三体", "刘慈欣", 300)
print(book == book2)  # True

常用魔术方法:

方法触发时机
__init__创建对象
__str__str(obj)print(obj)
__repr__repr(obj),交互式显示
__len__len(obj)
__eq__obj1 == obj2
__lt__obj1 < obj2
__getitem__obj[key]
__call__obj() 把对象当函数调用

5) 常用标准库速览

datetime:时间处理

from datetime import datetime, timedelta

now = datetime.now()
print(now)                    # 2024-01-15 14:30:00.123456
print(now.year, now.month, now.day)

# 格式化
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # 2024-01-15 14:30:00

# 解析字符串
birth = datetime.strptime("1990-05-20", "%Y-%m-%d")

# 时间计算
future = now + timedelta(days=7)
print(f"一周后:{future}")

random:随机数

import random

print(random.randint(1, 100))      # 随机整数 [1, 100]
print(random.random())             # 随机浮点数 [0, 1)
print(random.choice(["苹果", "香蕉", "橙子"]))  # 随机选择
random.shuffle([1, 2, 3, 4, 5])    # 随机打乱(原地修改)

re:正则表达式

import re

text = "我的邮箱是 test@example.com,电话是 138-1234-5678"

# 查找邮箱
email_pattern = r'\w+@\w+\.\w+'
email = re.search(email_pattern, text)
if email:
    print(email.group())  # test@example.com

# 替换
new_text = re.sub(r'\d{3}-\d{4}-\d{4}', '***-****-****', text)

6) 实战项目:学生管理系统(OOP 版)

把前面学的串起来,做一个完整的小项目。

功能需求:

  1. 添加学生(姓名、年龄、成绩)
  2. 删除学生
  3. 查询学生信息
  4. 显示所有学生
  5. 保存到文件 / 从文件加载
  6. 统计平均分、最高分、最低分

项目结构:

student_system/
├── main.py
├── student.py      # Student 类
└── manager.py      # 管理逻辑

student.py:

class Student:
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score
    
    def __str__(self):
        return f"{self.name}({self.age}岁): {self.score}分"
    
    def to_dict(self):
        """转为字典,用于 JSON 序列化"""
        return {
            "name": self.name,
            "age": self.age,
            "score": self.score
        }
    
    @classmethod
    def from_dict(cls, data):
        """从字典创建对象"""
        return cls(data["name"], data["age"], data["score"])

manager.py:

import json
from student import Student

class StudentManager:
    def __init__(self, data_file="students.json"):
        self.students = []
        self.data_file = data_file
        self.load_data()
    
    def add_student(self, name, age, score):
        student = Student(name, age, score)
        self.students.append(student)
        print(f"添加成功:{student}")
    
    def remove_student(self, name):
        for s in self.students:
            if s.name == name:
                self.students.remove(s)
                print(f"已删除:{name}")
                return
        print(f"未找到:{name}")
    
    def show_all(self):
        if not self.students:
            print("暂无学生")
            return
        print(f"\n共有 {len(self.students)} 名学生:")
        print("-" * 30)
        for s in self.students:
            print(s)
        print("-" * 30)
    
    def get_stats(self):
        if not self.students:
            return None
        scores = [s.score for s in self.students]
        return {
            "avg": sum(scores) / len(scores),
            "max": max(scores),
            "min": min(scores)
        }
    
    def save_data(self):
        data = [s.to_dict() for s in self.students]
        with open(self.data_file, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        print("数据已保存")
    
    def load_data(self):
        try:
            with open(self.data_file, "r", encoding="utf-8") as f:
                data = json.load(f)
                self.students = [Student.from_dict(d) for d in data]
            print(f"已加载 {len(self.students)} 名学生")
        except FileNotFoundError:
            print("暂无数据文件,将创建新文件")

main.py:

from manager import StudentManager

def main():
    manager = StudentManager()
    
    while True:
        print("\n=== 学生管理系统 ===")
        print("1. 添加学生")
        print("2. 删除学生")
        print("3. 显示全部")
        print("4. 统计信息")
        print("5. 保存并退出")
        
        choice = input("请选择:")
        
        if choice == "1":
            name = input("姓名:")
            age = int(input("年龄:"))
            score = float(input("成绩:"))
            manager.add_student(name, age, score)
        
        elif choice == "2":
            name = input("要删除的姓名:")
            manager.remove_student(name)
        
        elif choice == "3":
            manager.show_all()
        
        elif choice == "4":
            stats = manager.get_stats()
            if stats:
                print(f"平均分:{stats['avg']:.2f}")
                print(f"最高分:{stats['max']}")
                print(f"最低分:{stats['min']}")
        
        elif choice == "5":
            manager.save_data()
            print("再见!")
            break
        
        else:
            print("无效选择")

if __name__ == "__main__":
    main()

7) 下节课预告:从黄金到王者

下节课我们将学习:

  • 高级 Python 特性:生成器、装饰器、上下文管理器
  • 异常处理进阶:自定义异常、异常链
  • 并发编程:多线程、多进程、异步 IO
  • 数据库操作:SQLite、SQLAlchemy
  • Web 开发入门:Flask 框架,做个真正的网站
  • 项目实战:开发一个完整的 Web 应用

8) 学习建议

  1. 理解 OOP 思想:类、对象、继承、封装、多态,不只是语法,更是思维方式
  2. 多读优秀代码:GitHub 上的开源项目,学习别人的设计
  3. 画类图:复杂项目先画 UML 类图,理清关系再写代码
  4. 不要过度设计:小项目用函数就够了,大项目才需要 OOP
  5. 坚持重构:代码是写给人看的,顺便给机器执行,不断优化可读性

记住: 从面向过程到面向对象,是一次思维跃迁。一开始觉得别扭很正常,写多了就顺了。

遇到问题? 百度一下,或者放一放。有时候大脑需要后台编译。

祝编程愉快!Code is poetry! 🐍


课后作业:

  1. 完善学生管理系统:添加修改学生信息、按成绩排序、搜索功能
  2. 用 OOP 实现一个图书管理系统
  3. 学习使用 requests 库,写个爬取天气信息的脚本
  4. 尝试用 Flask 做个简单的 Web 版学生管理(预习)

下节课见!

0
快来点个赞吧

发表评论

隐私评论

评论列表

来写一个评论吧