🐍 Python 第三课:从白银到黄金——文件操作、模块与面向对象
12
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.jsonutils.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 | 日期时间 |
json | JSON 处理 |
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 版)
把前面学的串起来,做一个完整的小项目。
功能需求:
- 添加学生(姓名、年龄、成绩)
- 删除学生
- 查询学生信息
- 显示所有学生
- 保存到文件 / 从文件加载
- 统计平均分、最高分、最低分
项目结构:
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) 学习建议
- 理解 OOP 思想:类、对象、继承、封装、多态,不只是语法,更是思维方式
- 多读优秀代码:GitHub 上的开源项目,学习别人的设计
- 画类图:复杂项目先画 UML 类图,理清关系再写代码
- 不要过度设计:小项目用函数就够了,大项目才需要 OOP
- 坚持重构:代码是写给人看的,顺便给机器执行,不断优化可读性
记住: 从面向过程到面向对象,是一次思维跃迁。一开始觉得别扭很正常,写多了就顺了。
遇到问题? 百度一下,或者放一放。有时候大脑需要后台编译。
祝编程愉快!Code is poetry! 🐍
课后作业:
- 完善学生管理系统:添加修改学生信息、按成绩排序、搜索功能
- 用 OOP 实现一个图书管理系统
- 学习使用
requests库,写个爬取天气信息的脚本 - 尝试用 Flask 做个简单的 Web 版学生管理(预习)
下节课见!
0
快来点个赞吧
发表评论
评论列表