python(二)

22 阅读22分钟

Python 面向对象与高级特性学习笔记(下篇:OOP ~ 文件 ~ 异常 ~ 综合项目)

涵盖面向对象编程(封装、继承、多态)、异常处理、文件操作、JSON 序列化、综合项目实战。


目录


第六章:面向对象编程基础(33-34)

6.1 类的定义与实例化

核心思想: 万物皆对象。类是创建对象的模板,对象是类的具体实例。

定义语法:


    
    
    
  # 定义类
class Car:
    pass

# 创建对象(实例化)
c1 = Car()

# 动态添加属性
c1.brand = "BMW"
c1.name = "X5"
c1.price = 500000

# 查看对象的所有属性
print(c1.__dict__)  # {'brand': 'BMW', 'name': 'X5', 'price': 500000}

类名命名规范 — 大驼峰命名法:

正确错误
MyClassmy_class
StudentInfouserinfo
HttpServerhttp_server

6.2 构造方法 __init__ 与 self

推荐写法 — 使用 __init__ 初始化属性:


    
    
    
  class Car:
    def __init__(self, brand, name, price):
        self.brand = brand
        self.name = name
        self.price = price

# 创建对象时直接传入属性值
c1 = Car("BMW", "X5", 500000)
print(c1.brand)  # BMW

__init__ 方法详解:

要点说明
触发时机使用 类名() 创建对象时自动调用
作用给新对象赋予初始属性
命名特征前后各两个下划线 __init__

self 参数详解:

要点说明
含义代表当前正在操作的那个对象实例
核心作用区分不同对象的数据(p1 的数据存在 p1 的内存空间)
调用方式在类内部必须通过 self.属性名 或 self.方法名() 访问
自动传入调用方法时不需要手动传 self,Python 自动传入

6.3 实例方法

实例方法是定义在类中的函数,用于描述对象的行为。

语法:


    
    
    
  class Car:
    def __init__(self, brand, name, price):
        self.brand = brand
        self.name = name
        self.price = price

    def running(self):
        print(f"{self.brand} {self.name} 正在高速行驶...")

    def total_cost(self, discount, rate):
        return self.price * discount + self.price * rate

c1 = Car("BMW", "X5", 500000)
c1.running()                          # BMW X5 正在高速行驶...
final_price = c1.total_cost(0.9, 0.1) # 提车总价

调用规则:

  • • 定义时:第一个参数必须是 self
  • • 调用时:不需要传 self,Python 自动将调用者传入

6.4 魔法方法

魔法方法是 Python 类中以双下划线包裹的特殊方法,通过特定操作隐式触发。

魔法方法分类速查表
类别魔法方法触发时机说明
构造与初始化__new__创建对象时(在 __init__ 之前)控制对象生成(单例模式常用)
__init__对象创建后初始化属性
字符串表示__str__print() 或 str()返回用户友好的描述
__repr__交互式控制台直接输入对象返回开发者调试信息
运算符重载__add__+ 运算符自定义加法
__sub__- 运算符自定义减法
__eq__== 运算符自定义相等判断
大小比较__lt__<小于
__le__<=小于等于
__gt__>大于
__ge__>=大于等于
属性访问__getattr__访问不存在的属性提供默认值,防止报错
__setattr__给属性赋值拦截并验证赋值
上下文管理__enter__进入 with 块打开资源
__exit__退出 with 块关闭资源
实例:Vector 向量类

    
    
    
  class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        return NotImplemented

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1)       # 触发 __str__ → Vector(1, 2)
v3 = v1 + v2    # 触发 __add__
print(v3)       # Vector(4, 6)
实例属性 vs 类属性

    
    
    
  class MyClass:
    class_attr = "共享数据"    # 类属性:所有对象共享
    def __init__(self):
        self.instance_attr = 0  # 实例属性:每个对象独立
类型归属特点
实例属性每个具体对象各对象独立,互不影响
类属性类本身所有实例共享

第七章:异常处理(35)

7.1 try / except / finally

语法结构:


    
    
    
  try:
    # 可能出现异常的业务代码
    print(my_name)  # my_name 未定义,会报错
except NameError as e:
    # 捕获 NameError 类型的异常
    print("程序运行报错,错误信息:", e)
finally:
    # 无论是否报错,都会执行
    print("释放资源 ~")

执行流程:


    
    
    
  try 代码块
    ├── 无异常 → 跳过 except → 执行 finally → 继续后续代码
    └── 有异常 → 匹配 except → 执行 except 代码 → 执行 finally → 继续后续代码

7.2 常见异常类型

异常类型触发场景示例
NameError使用未定义的变量print(my_name)
TypeError类型不匹配"hello" + 5
ValueError值不合法int("abc")
IndexError索引越界lst[100]
KeyError字典 key 不存在d["不存在的key"]
FileNotFoundError文件不存在open("不存在.txt")
ZeroDivisionError除以零1 / 0
AttributeError对象没有该属性"str".foo()

捕获多种异常:


    
    
    
  try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"除零错误: {e}")
except Exception as e:
    print(f"未知错误: {e}")

第八章:文件操作(35-36)

8.1 文本文件读写

基本方式(手动关闭)

    
    
    
  f = open("望庐山瀑布.txt", "r", encoding="utf-8")
content = f.read()
print(content)
f.close()  # 必须手动关闭!
推荐方式(with 语句自动关闭)

    
    
    
  with open("望庐山瀑布.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)
# 离开 with 块后,文件自动关闭
文件打开模式
模式说明
"r"只读(默认),文件不存在则报错
"w"写入,文件不存在则创建,存在则覆盖
"a"追加,文件不存在则创建
"r+"读写
带异常处理的文件读取

    
    
    
  try:
    with open("望庐山瀑布.txt", "r", encoding="utf-8") as f:
        content = f.read()
        print(content)
except FileNotFoundError:
    print(f"错误:找不到文件")
except Exception as e:
    print(f"发生未知错误: {e}")

8.2 JSON 序列化与反序列化

JSON 是一种通用的数据交换格式,Python 通过 json 模块实现序列化/反序列化。

写入 JSON(序列化)

    
    
    
  import json
import os

obj = {
    "name": "张三",
    "age": 18,
    "gender": "男",
    "hobbies": ["reading", "swimming"]
}

os.makedirs("resources", exist_ok=True)

with open("resources/session.json", "w", encoding="utf-8") as f:
    json.dump(obj, f, ensure_ascii=False, indent=2)
    # ensure_ascii=False: 保留中文字符(不转成 \uXXXX)
    # indent=2: 格式化缩进,便于阅读
读取 JSON(反序列化)

    
    
    
  import json

with open("resources/session.json", "r", encoding="utf-8") as f:
    loaded_obj = json.load(f)

print(loaded_obj)            # Python 字典
print(type(loaded_obj))      # <class 'dict'>
print(loaded_obj['name'])    # 张三
json 模块方法对比
方法方向说明
json.dump(obj, file)Python → 文件将 Python 对象序列化写入文件
json.load(file)文件 → Python从文件反序列化为 Python 对象
json.dumps(obj)Python → 字符串将 Python 对象序列化为 JSON 字符串
json.loads(string)字符串 → Python将 JSON 字符串反序列化为 Python 对象
Python 与 JSON 类型映射
PythonJSON
dict{} 对象
list[] 数组
str"..." 字符串
int / float数字
True / Falsetrue / false
Nonenull

8.3 CSV 文件读写

CSV(Comma-Separated Values)是最常见的表格数据格式,Python 通过内置 csv 模块操作。

写入 CSV

    
    
    
  import csv

students = [
    ["姓名", "年龄", "成绩"],
    ["张三", 18, 92],
    ["李四", 19, 85],
    ["王五", 17, 97],
]

with open("students.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerows(students)
    # writerows 一次性写入多行;也可以用 writerow 逐行写入

注意: newline="" 防止 Windows 下出现空行。

读取 CSV

    
    
    
  import csv

with open("students.csv", "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)
# 输出:
# ['姓名', '年龄', '成绩']
# ['张三', '18', '92']
# ['李四', '19', '85']
# ['王五', '17', '97']
使用 DictReader / DictWriter(字典方式)

以字典形式读写 CSV,自动用第一行作为字段名:


    
    
    
  import csv

# 写入
data = [
    {"name": "张三", "age": 18, "score": 92},
    {"name": "李四", "age": 19, "score": 85},
]

with open("students_dict.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "age", "score"])
    writer.writeheader()   # 写入表头
    writer.writerows(data) # 写入数据行

# 读取
with open("students_dict.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["score"])
# 输出:
# 张三 92
# 李四 85
csv 模块方法对比
类方法说明
writerwriterow(row)写入一行(列表)
writerwriterows(rows)写入多行
DictWriterwriteheader()写入表头
DictWriterwriterows(rows)写入多行(字典列表)
reader迭代每行返回一个列表
DictReader迭代每行返回一个字典(key 为表头)

8.4 二进制文件操作

二进制文件用于存储图片、音频、视频、序列化对象等非文本数据。打开模式使用 "rb"(读)或 "wb"(写),不需要 encoding 参数。

读写二进制文件

    
    
    
  # 写入二进制数据
with open("data.bin", "wb") as f:
    f.write(b"\x89PNG\r\n\x1a\n")  # bytes 字面量
    f.write(bytes([0xFF, 0xD8, 0xFF]))  # 从列表构造 bytes

# 读取二进制数据
with open("data.bin", "rb") as f:
    data = f.read()
    print(data)          # b'\x89PNG\r\n\x1a\n...'
    print(type(data))    # <class 'bytes'>
    print(len(data))     # 字节数
文件复制(二进制方式)

    
    
    
  def copy_file(src: str, dst: str):
    """复制任意类型的文件(图片、视频等)"""
    with open(src, "rb") as f_src:
        data = f_src.read()
    with open(dst, "wb") as f_dst:
        f_dst.write(data)
    print(f"复制完成:{src} → {dst}")

copy_file("photo.jpg", "photo_copy.jpg")
分块读取大文件

    
    
    
  def read_large_binary(path: str, chunk_size: int = 8192):
    """分块读取,避免一次性加载大文件到内存"""
    with open(path, "rb") as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            # 处理每个 chunk
            print(f"读取了 {len(chunk)} 字节")
bytes 与 str 互转

    
    
    
  # 编码:str → bytes
text = "你好世界"
data = text.encode("utf-8")
print(data)  # b'\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c'

# 解码:bytes → str
recovered = data.decode("utf-8")
print(recovered)  # 你好世界
常见二进制操作模式
模式说明典型用途
"rb"只读二进制读取图片、音频
"wb"写入二进制(覆盖)保存下载的文件
"ab"追加二进制拼接文件
"r+b"读写二进制修改文件特定位置

8.5 路径操作 — pathlib 模块

pathlib 是 Python 3 推荐的路径操作库,用面向对象的方式处理文件路径,跨平台兼容。

创建 Path 对象

    
    
    
  from pathlib import Path

# 当前目录
p = Path(".")

# 绝对路径
p = Path("/Users/demo/documents")

# 拼接路径(使用 / 运算符)
p = Path("data") / "2024" / "report.csv"
print(p)  # data/2024/report.csv

# 从字符串构造
p = Path("data/2024/report.csv")
常用路径属性

    
    
    
  p = Path("/Users/demo/documents/report.csv")

print(p.name)      # report.csv      — 文件名
print(p.stem)      # report          — 文件名(不含后缀)
print(p.suffix)    # .csv            — 后缀名
print(p.parent)    # /Users/demo/documents — 父目录
print(p.anchor)    # /               — 根路径
文件与目录操作

    
    
    
  from pathlib import Path

p = Path("data/output")

# 创建目录(递归)
p.mkdir(parents=True, exist_ok=True)

# 判断
print(p.exists())     # True / False
print(p.is_file())    # 是否是文件
print(p.is_dir())     # 是否是目录

# 列出目录下所有 .py 文件
for f in Path(".").glob("**/*.py"):
    print(f)

# 读取 / 写入文本
Path("hello.txt").write_text("Hello, pathlib!", encoding="utf-8")
content = Path("hello.txt").read_text(encoding="utf-8")
print(content)

# 读取 / 写入二进制
data = Path("photo.jpg").read_bytes()
Path("photo_copy.jpg").write_bytes(data)

# 删除文件
Path("hello.txt").unlink(missing_ok=True)

# 重命名
Path("old.txt").rename("new.txt")
pathlib vs os.path 对比
操作os.path(传统)pathlib(推荐)
拼接路径os.path.join("a", "b")Path("a") / "b"
获取文件名os.path.basename(p)Path(p).name
获取后缀os.path.splitext(p)[1]Path(p).suffix
判断存在os.path.exists(p)Path(p).exists()
列出文件os.listdir(".")list(Path(".").iterdir())
读取文件open(p).read()Path(p).read_text()
综合示例:批量重命名文件

    
    
    
  from pathlib import Path

folder = Path("photos")
folder.mkdir(exist_ok=True)

# 假设目录下有 1.jpg, 2.jpg, 3.jpg ...
for i, jpg in enumerate(sorted(folder.glob("*.jpg")), start=1):
    new_name = folder / f"photo_{i:03d}{jpg.suffix}"
    jpg.rename(new_name)
    print(f"{jpg.name} → {new_name.name}")

第九章:面向对象三大特性

9.1 封装(37)

核心思想: 把数据和操作数据的方法捆绑在一起,通过访问控制隐藏内部细节。

三种访问级别
级别命名约定说明
公有self.name外部可直接访问
受保护self._age单下划线,约定不建议外部访问
私有self.__health双下划线,外部无法直接访问(名称改写)
示例:Animal 类

    
    
    
  class Animal:
    kingdom = "动物界"  # 类属性

    def __init__(self, name: str, age: int, health: int = 100):
        self.name = name            # 公有
        self._age = age             # 受保护
        self.__health = health      # 私有

    # Getter
    def get_health(self) -> int:
        return self.__health

    # Setter(带校验)
    def set_health(self, value: int):
        if value < 0:
            raise ValueError("健康值不能为负数")
        self.__health = value
@property 装饰器

让方法像属性一样使用,同时保留校验逻辑:


    
    
    
  class Car:
    def __init__(self, brand, model, fuel=100.0):
        self.brand = brand
        self.model = model
        self.__fuel = fuel
        self.__mileage = 0.0

    @property
    def fuel(self) -> float:
        """只读属性:返回当前油量"""
        return self.__fuel

    @property
    def mileage(self) -> float:
        return self.__mileage

    def drive(self, distance: float):
        consumption = distance * 0.1
        if consumption > self.__fuel:
            raise ValueError(f"油量不足!")
        self.__fuel -= consumption
        self.__mileage += distance

car = Car("比亚迪", "汉EV")
car.drive(200)
print(car.fuel)     # 通过 @property 访问,像属性一样
print(car.mileage)  # 只读属性

9.2 继承(38)

核心思想: 子类自动拥有父类的属性和方法,实现代码复用;子类可以扩展或覆盖(override)父类行为。

基本语法

    
    
    
  class Animal:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def speak(self) -> str:
        return f"{self.name} 发出了声音"

    def eat(self, food: str):
        print(f"{self.name} 正在吃 {food}")


class Dog(Animal):
    def __init__(self, name: str, age: int, breed: str):
        super().__init__(name, age)  # 调用父类 __init__
        self.breed = breed           # 子类新增属性

    def speak(self) -> str:          # 重写(Override)父类方法
        return f"{self.name} 汪汪叫!"

    def fetch(self, item: str):      # 子类新增方法
        print(f"{self.name} 去捡 {item} 了")
关键概念
概念说明
super().__init__()调用父类的构造方法,必须写在子类 __init__ 中
方法重写(Override)子类定义与父类同名的方法,覆盖父类行为
isinstance(obj, cls)判断对象是否是某类(或其子类)的实例
issubclass(A, B)判断 A 是否是 B 的子类
示例:Vehicle 继承体系

    
    
    
  class Vehicle:
    def __init__(self, brand, speed):
        self.brand = brand
        self.speed = speed
    def move(self):
        print(f"{self.brand} 正在行驶")

class Car(Vehicle):
    def __init__(self, brand, speed, seats):
        super().__init__(brand, speed)
        self.seats = seats
    def move(self):  # 重写
        print(f"{self.brand} 轿车({self.seats}座)正在行驶")

class Truck(Vehicle):
    def __init__(self, brand, speed, payload):
        super().__init__(brand, speed)
        self.payload = payload
    def move(self):  # 重写
        print(f"{self.brand} 卡车(载重{self.payload}t)正在行驶")

9.3 多继承与 MRO(39)

核心思想: 一个子类同时继承多个父类;Python 用 C3 线性化算法(MRO) 确定方法查找顺序。

Mixin 模式 — 功能组合

    
    
    
  class Flyable:
    def fly(self):
        print(f"{self.__class__.__name__} 正在飞翔")

class Swimmable:
    def swim(self):
        print(f"{self.__class__.__name__} 正在游泳")

class Animal:
    def __init__(self, name):
        self.name = name

class Duck(Flyable, Swimmable, Animal):
    def __init__(self, name):
        Animal.__init__(self, name)
    def speak(self):
        return f"{self.name} 嘎嘎嘎!"

duck = Duck("唐老鸭")
duck.fly()    # 来自 Flyable
duck.swim()   # 来自 Swimmable
MRO 方法解析顺序

    
    
    
  print(Duck.__mro__)
# (<class 'Duck'>, <class 'Flyable'>, <class 'Swimmable'>, <class 'Animal'>, <class 'object'>)

同名方法冲突: 当多个父类有同名方法时,按 MRO 顺序,排在前面的优先。

菱形继承与 super() 协作

    
    
    
  class A:
    def __init__(self):
        super().__init__()

class B(A):
    def __init__(self):
        super().__init__()

class C(A):
    def __init__(self):
        super().__init__()

class D(B, C):
    """MRO: D -> B -> C -> A -> object"""
    def __init__(self):
        super().__init__()  # 沿 MRO 链调用,每个 __init__ 只执行一次

9.4 多态(40)

核心思想: 同一个接口,不同的类有不同的实现;调用方无需关心对象的具体类型。

方式一:抽象基类(ABC)

    
    
    
  from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name: str):
        self.name = name

    @abstractmethod
    def speak(self) -> str:
        pass  # 抽象方法,子类必须实现

class Dog(Animal):
    def speak(self): return f"{self.name} 汪汪!"

class Cat(Animal):
    def speak(self): return f"{self.name} 喵喵~"

# 多态的核心用法
def make_all_speak(animals: list[Animal]):
    for animal in animals:
        print(animal.speak())  # 行为由对象的真实类型决定

make_all_speak([Dog("旺财"), Cat("咪咪")])

抽象基类的特点:

  • • 使用 ABC 和 @abstractmethod 定义
  • • 抽象类不能直接实例化,会报 TypeError
  • • 子类必须实现所有抽象方法,否则也不能实例化
方式二:鸭子类型(Duck Typing)

    
    
    
  class Robot:
    """不继承 Animal,但也有 speak 方法"""
    def speak(self):
        return f"机器人:您好"

def make_speak(obj):
    """只要对象有 speak() 方法就能调用,不检查类型"""
    print(obj.speak())

make_speak(Dog("小黑"))       # OK
make_speak(Robot("GPT-9000")) # 也 OK!

鸭子类型哲学: "如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。"


第十章:综合项目 — 图书管理系统(41)

这是一个综合运用封装、继承、多态、异常处理的完整项目。

10.1 项目结构


    
    
    
  图书管理系统/
├── main.py        # 主程序入口,初始化数据并运行场景
├── book.py        # Book 类:图书实体
├── member.py      # Member 类体系:会员(普通/VIP)
└── library.py     # Library 类:图书馆管理核心

设计模式:

  • • Book — 数据实体(封装库存管理)
  • • Member — 抽象基类 + 继承(RegularMember / VipMember)
  • • Library — 业务控制器(协调 Book 和 Member 的交互)

10.2 Book 类


    
    
    
  class Book:
    def __init__(self, book_id: str, title: str, author: str, stock: int = 1):
        self.book_id = book_id
        self.title = title
        self.author = author
        self.__stock = stock      # 私有属性:库存

    @property
    def stock(self) -> int:       # 只读属性
        return self.__stock

    def borrow(self):
        if self.__stock <= 0:
            raise ValueError(f"《{self.title}》库存不足")
        self.__stock -= 1

    def return_book(self):
        self.__stock += 1

    def __repr__(self) -> str:
        return f"[{self.book_id}] 《{self.title}》 / {self.author}(库存:{self.__stock})"

设计要点:

  • • __stock 是私有属性,外部无法直接修改
  • • stock 是只读的 @property,只能看不能改
  • • 借书/还书通过方法控制,内部做库存校验

10.3 Member 类体系

继承关系:


    
    
    
  Member(基类)
├── RegularMember(普通会员,最多借 3 本)
└── VipMember(VIP 会员,最多借 6 + vip_level 本)

    
    
    
  class Member:
    def __init__(self, card_id, password, name):
        self.card_id = card_id
        self.__password = password      # 私有属性:密码
        self.name = name
        self._borrowed: list[Book] = [] # 受保护:借阅列表

    def check_password(self, password) -> bool:
        return self.__password == password

    def max_borrow(self) -> int:
        raise NotImplementedError("子类必须实现")

    def borrow_book(self, book: Book):
        if len(self._borrowed) >= self.max_borrow():
            print(f"借书失败:已达最大借书数量({self.max_borrow()} 本)")
            return
        book.borrow()
        self._borrowed.append(book)

    def return_book(self, book_id: str):
        for book in self._borrowed:
            if book.book_id == book_id:
                self._borrowed.remove(book)
                book.return_book()
                return


class RegularMember(Member):
    def max_borrow(self) -> int:
        return 3

class VipMember(Member):
    def __init__(self, card_id, password, name, vip_level=1):
        super().__init__(card_id, password, name)
        self.vip_level = vip_level

    def max_borrow(self) -> int:
        return 6 + self.vip_level

多态体现: borrow_book() 中调用 self.max_borrow(),具体行为由子类决定。


10.4 Library 类


    
    
    
  class Library:
    def __init__(self, name: str):
        self.name = name
        self.__books: dict[str, Book] = {}
        self.__members: dict[str, Member] = {}
        self.__current: Optional[Member] = None  # 当前登录用户

    def login(self, card_id, password) -> bool:
        member = self.__members.get(card_id)
        if member is None:
            print("登录失败:卡号不存在")
            return False
        if not member.check_password(password):
            print("登录失败:密码错误")
            return False
        self.__current = member
        return True

    def borrow(self, book_id: str):
        member = self._require_login()
        if not member:
            return
        book = self.__books.get(book_id)
        try:
            member.borrow_book(book)
        except ValueError as e:
            print(f"借书失败:{e}")

设计要点:

  • • __current 追踪当前登录用户,所有操作需要先登录
  • • _require_login() 是内部辅助方法,检查登录状态
  • • 使用 try/except 处理库存不足等业务异常

10.5 完整可运行代码

以下是整个项目的完整代码,包含四个文件。

book.py — 图书实体

    
    
    
  class Book:
    def __init__(self, book_id: str, title: str, author: str, stock: int = 1):
        self.book_id = book_id
        self.title = title
        self.author = author
        self.__stock = stock

    @property
    def stock(self) -> int:
        return self.__stock

    def borrow(self):
        if self.__stock <= 0:
            raise ValueError(f"《{self.title}》库存不足")
        self.__stock -= 1

    def return_book(self):
        self.__stock += 1

    def __repr__(self) -> str:
        return f"[{self.book_id}] 《{self.title}》 / {self.author}(库存:{self.__stock})"
member.py — 会员类体系

    
    
    
  from __future__ import annotations
from typing import Optional
from book import Book


class Member:
    def __init__(self, card_id: str, password: str, name: str):
        self.card_id = card_id
        self.__password = password
        self.name = name
        self._borrowed: list[Book] = []

    def check_password(self, password: str) -> bool:
        return self.__password == password

    def max_borrow(self) -> int:
        raise NotImplementedError("子类必须实现 max_borrow()")

    def borrow_limit_desc(self) -> str:
        raise NotImplementedError("子类必须实现 borrow_limit_desc()")

    def borrow_book(self, book: Book):
        if len(self._borrowed) >= self.max_borrow():
            print(f"借书失败:{self.name} 已达最大借书数量({self.max_borrow()} 本)")
            return
        book.borrow()
        self._borrowed.append(book)
        print(f"借书成功:{self.name} 借阅了 《{book.title}》")

    def return_book(self, book_id: str):
        for book in self._borrowed:
            if book.book_id == book_id:
                self._borrowed.remove(book)
                book.return_book()
                print(f"还书成功:{self.name} 归还了 《{book.title}》")
                return
        print(f"还书失败:借阅列表中没有 ID 为 {book_id!r} 的书")

    def show_borrowed(self):
        if not self._borrowed:
            print(f"{self.name} 当前没有借阅任何书籍")
            return
        print(f"{self.name} 当前借阅({len(self._borrowed)} 本):")
        for book in self._borrowed:
            print(f"  - {book}")

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(card={self.card_id!r}, name={self.name!r})"


class RegularMember(Member):
    def max_borrow(self) -> int:
        return 3

    def borrow_limit_desc(self) -> str:
        return "普通会员,最多可借 3 本"


class VipMember(Member):
    def __init__(self, card_id: str, password: str, name: str, vip_level: int = 1):
        super().__init__(card_id, password, name)
        self.vip_level = vip_level

    def max_borrow(self) -> int:
        return 6 + self.vip_level

    def borrow_limit_desc(self) -> str:
        return f"VIP{self.vip_level} 会员,最多可借 {self.max_borrow()} 本"
library.py — 图书馆管理核心

    
    
    
  from __future__ import annotations
from typing import Optional
from book import Book
from member import Member


class Library:
    def __init__(self, name: str):
        self.name = name
        self.__books: dict[str, Book] = {}
        self.__members: dict[str, Member] = {}
        self.__current: Optional[Member] = None

    def add_book(self, book: Book):
        self.__books[book.book_id] = book

    def register_member(self, member: Member):
        self.__members[member.card_id] = member

    def login(self, card_id: str, password: str) -> bool:
        member = self.__members.get(card_id)
        if member is None:
            print("登录失败:卡号不存在")
            return False
        if not member.check_password(password):
            print("登录失败:密码错误")
            return False
        self.__current = member
        print(f"登录成功!欢迎,{member.name}({member.borrow_limit_desc()})")
        return True

    def logout(self):
        if self.__current:
            print(f"再见,{self.__current.name}!")
        self.__current = None

    def _require_login(self) -> Optional[Member]:
        if self.__current is None:
            print("请先登录")
            return None
        return self.__current

    def borrow(self, book_id: str):
        member = self._require_login()
        if not member:
            return
        book = self.__books.get(book_id)
        if not book:
            print(f"借书失败:图书馆没有 ID 为 {book_id!r} 的书")
            return
        try:
            member.borrow_book(book)
        except ValueError as e:
            print(f"借书失败:{e}")

    def return_book(self, book_id: str):
        member = self._require_login()
        if member:
            member.return_book(book_id)

    def show_my_books(self):
        member = self._require_login()
        if member:
            member.show_borrowed()

    def show_all_books(self):
        print(f"\n【{self.name}馆藏书目】")
        for book in self.__books.values():
            print(f"  {book}")
main.py — 主程序入口与完整运行场景

    
    
    
  from book import Book
from member import RegularMember, VipMember
from library import Library


def init_library() -> Library:
    """初始化图书馆,添加书籍和会员"""
    lib = Library("社区图书馆")

    # 添加图书
    books = [
        Book("AI001", "LangChain入门与实践", "李智慧", stock=8),
        Book("AI002", "深度学习基础教程", "王小明", stock=5),
        Book("AI003", "Python数据科学手册", "Jake VanderPlas", stock=3),
        Book("CS001", "算法导论(第4版)", "Thomas Cormen", stock=2),
        Book("CS002", "操作系统概念", "Abraham Silberschatz", stock=4),
        Book("LT001", "红楼梦", "曹雪芹", stock=6),
        Book("LT002", "三体", "刘慈欣", stock=10),
    ]
    for book in books:
        lib.add_book(book)

    # 注册会员
    lib.register_member(RegularMember("C001", "123456", "张三"))
    lib.register_member(RegularMember("C002", "654321", "王五"))
    lib.register_member(VipMember("V001", "abcdef", "李四", vip_level=2))
    lib.register_member(VipMember("V002", "vip888", "赵六", vip_level=1))

    return lib


def print_separator(title: str):
    print(f"\n{'='*50}")
    print(f"  {title}")
    print(f"{'='*50}")


if __name__ == "__main__":
    lib = init_library()

    # ===== 场景一:查看馆藏 =====
    print_separator("场景一:查看馆藏")
    lib.show_all_books()

    # ===== 场景二:未登录尝试借书 =====
    print_separator("场景二:未登录尝试借书")
    lib.borrow("AI001")          # → 请先登录
    lib.show_my_books()          # → 请先登录

    # ===== 场景三:密码错误 =====
    print_separator("场景三:登录失败")
    lib.login("C001", "wrong")   # → 密码错误
    lib.login("C999", "123456")  # → 卡号不存在

    # ===== 场景四:普通会员借书(上限 3 本)=====
    print_separator("场景四:普通会员张三借书")
    lib.login("C001", "123456")
    lib.borrow("AI001")          # 借书成功
    lib.borrow("AI002")          # 借书成功
    lib.borrow("CS001")          # 借书成功(已达上限 3 本)
    lib.show_my_books()
    lib.borrow("AI003")          # → 已达最大借书数量

    # ===== 场景五:还书后再借 =====
    print_separator("场景五:还书后再借")
    lib.return_book("AI002")     # 归还《深度学习基础教程》
    lib.show_my_books()
    lib.borrow("AI003")          # 还了一本,可以再借

    # ===== 场景六:还一本不存在的书 =====
    print_separator("场景六:异常还书")
    lib.return_book("NOTEXIST")  # → 借阅列表中没有

    # ===== 场景七:VIP2 会员借书(上限 8 本)=====
    print_separator("场景七:VIP2 会员李四借书")
    lib.login("V001", "abcdef")
    lib.borrow("AI001")
    lib.borrow("AI002")
    lib.borrow("AI003")
    lib.borrow("CS001")
    lib.borrow("CS002")
    lib.borrow("LT001")
    lib.borrow("LT002")
    lib.show_my_books()          # 已借 7 本
    lib.borrow("AI001")          # 第 8 本 → 成功(上限 8 本)
    lib.show_my_books()
    lib.borrow("CS002")          # 第 9 本 → 超出上限

    # ===== 场景八:库存耗尽 =====
    print_separator("场景八:库存耗尽测试")
    lib.login("C002", "654321")
    lib.borrow("CS001")          # CS001 库存 2,已被借走 2 本
    # → 《算法导论》库存不足

    # ===== 场景九:注销 =====
    print_separator("场景九:注销")
    lib.logout()
    lib.borrow("AI001")          # → 请先登录

    # ===== 最终状态 =====
    print_separator("最终馆藏状态")
    lib.show_all_books()
运行效果示例

    
    
    
  ==================================================
  场景一:查看馆藏
==================================================

【社区图书馆馆藏书目】
  [AI001] 《LangChain入门与实践》 / 李智慧(库存:8)
  [AI002] 《深度学习基础教程》 / 王小明(库存:5)
  [AI003] 《Python数据科学手册》 / Jake VanderPlas(库存:3)
  [CS001] 《算法导论(第4版)》 / Thomas Cormen(库存:2)
  [CS002] 《操作系统概念》 / Abraham Silberschatz(库存:4)
  [LT001] 《红楼梦》 / 曹雪芹(库存:6)
  [LT002] 《三体》 / 刘慈欣(库存:10)

==================================================
  场景二:未登录尝试借书
==================================================
请先登录
请先登录

==================================================
  场景四:普通会员张三借书
==================================================
登录成功!欢迎,张三(普通会员,最多可借 3 本)
借书成功:张三 借阅了 《LangChain入门与实践》
借书成功:张三 借阅了 《深度学习基础教程》
借书成功:张三 借阅了 《算法导论(第4版)》
张三 当前借阅(3 本):
  - [AI001] 《LangChain入门与实践》 / 李智慧(库存:7)
  - [AI002] 《深度学习基础教程》 / 王小明(库存:4)
  - [CS001] 《算法导论(第4版)》 / Thomas Cormen(库存:1)
借书失败:张三 已达最大借书数量(3 本)
项目知识点总结
章节在项目中的体现
封装Book.__stock 私有属性、Member.__password 私有属性、@property 只读库存
继承RegularMember / VipMember 继承 Member,super().__init__()
多态max_borrow() 和 borrow_limit_desc() 由子类实现,borrow_book() 统一调用
异常处理Book.borrow() 抛出 ValueError,Library.borrow() 中 try/except 捕获
魔法方法__repr__ 自定义对象打印格式
模块导入三个文件互相 import,from __future__ import annotations 延迟类型注解

附录:面向对象核心概念速查

三大特性对比

特性核心思想关键词示例
封装隐藏内部细节,暴露安全接口私有属性、@property、getter/settercar.__fuel 不可直接访问
继承子类复用父类代码,可扩展/覆盖super()、重写、isinstanceDog(Animal)
多态同一接口,不同实现抽象基类、鸭子类型animal.speak() 行为因类型而异

类的特殊属性/方法

名称说明
__dict__对象的所有属性(字典形式)
__class__对象所属的类
__mro__方法解析顺序(多继承查找顺序)
__name__类/模块的名称
__doc__文档字符串

设计原则总结

原则说明
单一职责一个类只做一件事(Book 管书、Member 管人、Library 管协调)
开闭原则对扩展开放,对修改关闭(新增 VIP 等级只需新建子类)
依赖倒置依赖抽象,不依赖具体(Library 依赖 Member 基类,不依赖具体子类)