当前位置:首页>python>Python 面向对象习题(十)

Python 面向对象习题(十)

  • 2026-09-18 18:28:39
Python 面向对象习题(十)

本文首发于微信公众号「拙见python」,基础板块第13 篇。适合人群:已阅读第 2 篇:Python 面向对象编程并动手敲过代码的读者。题目难度:⭐ → ⭐⭐⭐⭐⭐ 逐步提升,建议先自己写,再看答案。


做题须知

  • 每道题标注了考察知识点和难度星级
  • 先自己写,写不出来再看答案
  • 答案不是唯一的,能跑通且逻辑正确就行
  • 每道题答案下方附有思路解析

第 10 题:综合实战——银行账户系统 ⭐⭐⭐⭐⭐

考察知识点:全部 OOP 知识点综合——封装、继承、多态、@property、魔术方法、@dataclass、类方法、异常处理、设计原则

设计一个银行账户系统:

基类 BankAccount:

  1. 私有属性 __balance(余额)、受保护属性 _account_number、公开属性 owner

  2. @property 暴露 balance(只读)

  3. deposit(amount) 存款(金额必须 >0)

  4. withdraw(amount) 取款(金额必须 >0 且 ≤ 余额)

  5. transfer(target, amount) 转账到另一个账户

  6. __str__ 和 __repr__

  7. __add__ 实现账户合并(余额相加,返回新账户)

  8. 类属性 total_accounts 记录总账户数,类方法 get_total() 查询

  9. 自定义异常 InsufficientFundsError、InvalidAmountError

子类 SavingsAccount(储蓄账户):

  • 新增 interest_rate(利率)属性

  • 新增 apply_interest() 方法计算并存入利息

  • 重写 withdraw():每次取款额外收 1 元手续费

子类 CreditAccount(信用账户):

  • 新增 credit_limit(信用额度)属性

  • 重写 withdraw():允许透支到信用额度

  • 新增 available_credit() 方法返回可用额度

Bank 类(银行):

  • 管理所有账户(字典 account_number → BankAccount)

  • open_account(...) 开户

  • get_account(number) 查询账户

  • __len__、__contains__、__str__

参考答案:

from dataclasses import dataclassfrom typing import Optional, Dict# ========== 自定义异常 ==========class InvalidAmountError(Exception):    ”””金额无效异常”””    passclass InsufficientFundsError(Exception):    ”””余额不足异常”””    pass# ========== 基类:银行账户 ==========class BankAccount:    total_accounts = 0# 类属性:总账户数    def __init__(self, owner: str, account_number: str, balance: float = 0):        self.owner = owner        self._account_number = account_number        self.__balance = balance        BankAccount.total_accounts += 1# -------- @property:只读余额 --------    @property    def balance(self):        return self.__balance    @property    def account_number(self):        return self._account_number# -------- 存取款 --------    def deposit(self, amount: float):        if amount <= 0:            raise InvalidAmountError(f”存款金额必须大于 0,收到 {amount}”)        self.__balance += amount        return self.__balance    def withdraw(self, amount: float):        if amount <= 0:            raise InvalidAmountError(f”取款金额必须大于 0,收到 {amount}”)        if amount > self.__balance:            raise InsufficientFundsError(                f”余额不足:余额 {self.__balance},尝试取 {amount}”            )        self.__balance -= amount        return self.__balance# -------- 转账 --------    def transfer(self, target: 'BankAccount', amount: float):        ”””转账:从当前账户扣款,存入目标账户”””        self.withdraw(amount)# 可能抛 InsufficientFundsError        target.deposit(amount)        return f”{self.owner} → {target.owner}:¥{amount:.2f}”# -------- 魔术方法 --------    def __str__(self):        return f”账户[{self._account_number}] {self.owner} 余额:¥{self.__balance:.2f}”    def __repr__(self):        return f”BankAccount(owner={self.owner!r}, account_number={self._account_number!r}, balance={self.__balance})”    def __add__(self, other):        ”””合并账户:余额相加,生成新账户”””        new_number = f”{self._account_number}+{other._account_number}”        return BankAccount(f”{self.owner}&{other.owner}”, new_number, self.__balance + other.__balance)    def __eq__(self, other):        return self._account_number == other._account_number# -------- 类方法 --------    @classmethod    def get_total(cls):        return cls.total_accounts# ========== 子类:储蓄账户 ==========class SavingsAccount(BankAccount):    def __init__(self, owner, account_number, balance=0, interest_rate=0.03):        super().__init__(owner, account_number, balance)        self.interest_rate = interest_rate    def apply_interest(self):        ”””计算并存入利息”””        interest = self.balance * self.interest_rate        self.deposit(interest)        return interest    def withdraw(self, amount: float):        ”””重写:每次取款额外收 1 元手续费”””        total = amount + 1# 本金 + 手续费        if total > self.balance:            raise InsufficientFundsError(                f”余额不足(含手续费):需 {total},余额 {self.balance}”            )# 直接操作私有余额不可行(__balance 是名称重整的)# 所以先取本金+手续费,再单独扣除手续费        super().withdraw(total)        return self.balance    def __str__(self):        return f”储蓄账户[{self.account_number}] {self.owner} 余额:¥{self.balance:.2f}(利率{self.interest_rate:.1%})”# ========== 子类:信用账户 ========== class CreditAccount(BankAccount):    def __init__(self, owner, account_number, balance=0, credit_limit=5000):        super().__init__(owner, account_number, balance)        self.credit_limit = credit_limit    def withdraw(self, amount: float):        ”””重写:允许透支到信用额度”””        if amount <= 0:            raise InvalidAmountError(f”取款金额必须大于 0”)# 可用 = 余额 + 信用额度        available = self.balance + self.credit_limit        if amount > available:            raise InsufficientFundsError(                f”超出信用额度:可用 {available},尝试取 {amount}”            )# 直接调用父类 deposit/withdraw 无法透支,需特殊处理# 透支时余额变负        self._BankAccount__balance -= amount# 通过名称重整访问私有属性        return self.balance    def available_credit(self):        ”””可用额度 = 余额 + 信用额度”””        return self.balance + self.credit_limit    def __str__(self):        return f”信用账户[{self.account_number}] {self.owner} 余额:¥{self.balance:.2f}(额度{self.credit_limit})”# ========== Bank 类 ==========class Bank:    def __init__(self, name: str):        self.name = name        self._accounts: Dict[str, BankAccount] = {}    def open_account(self, account: BankAccount):        ”””开户”””        number = account.account_number        if number in self._accounts:            print(f”⚠️ 账号 {number} 已存在”)            return None        self._accounts[number] = account        print(f”🏦 {self.name} 开户成功:{account}”)        return account    def get_account(self, account_number: str) -> Optional[BankAccount]:        ”””查询账户”””        return self._accounts.get(account_number)    def all_balances(self):        ”””所有账户余额汇总”””        return sum(acc.balance for acc in self._accounts.values())    def __len__(self):        return len(self._accounts)    def __contains__(self, account_number: str):        return account_number in self._accounts    def __str__(self):        return f”{self.name}({len(self)} 个账户,总存款 ¥{self.all_balances():.2f})”# ========== 测试 ==========def main():    print(”=” * 55)    print(”  拙见python 银行账户系统演示”)    print(”=” * 55)    bank = Bank(”拙见银行”)# 开户    acc1 = SavingsAccount(”张三”, ”SA001”, 10000, interest_rate=0.035)    acc2 = CreditAccount(”李四”, ”CA001”, 2000, credit_limit=5000)    acc3 = BankAccount(”王五”, ”BA001”, 5000)    bank.open_account(acc1)    bank.open_account(acc2)    bank.open_account(acc3)    print(f”\n总账户数:{BankAccount.get_total()}”)# 存取款    print(”\n--- 存取款测试 ---”)    acc1.deposit(5000)    print(f”张三存款 5000 后:¥{acc1.balance:.2f}”)    acc3.withdraw(1000)    print(f”王五取款 1000 后:¥{acc3.balance:.2f}”)# 储蓄账户取款(含手续费)    acc1.withdraw(1000)    print(f”张三取款 1000(含1元手续费)后:¥{acc1.balance:.2f}”)# 信用账户透支    print(”\n--- 信用账户透支测试 ---”)    acc2.withdraw(6000)# 余额 2000 + 额度 5000 = 7000,可以取 6000    print(f”李四透支取款 6000 后:¥{acc2.balance:.2f}”)    print(f”李四可用额度:¥{acc2.available_credit():.2f}”)# 透支超限    try:        acc2.withdraw(5000)    except InsufficientFundsError as e:        print(f”❌ {e}”)# 利息    print(”\n--- 利息测试 ---”)    interest = acc1.apply_interest()    print(f”张三利息到账:¥{interest:.2f},新余额:¥{acc1.balance:.2f}”)# 转账    print(”\n--- 转账测试 ---”)    result = acc3.transfer(acc1, 500)    print(f”转账结果:{result}”)    print(f”王五余额:¥{acc3.balance:.2f}”)    print(f”张三余额:¥{acc1.balance:.2f}”)# 异常测试    print(”\n--- 异常测试 ---”)    try:        acc3.deposit(-100)    except InvalidAmountError as e:        print(f”❌ {e}”)    try:        acc3.withdraw(99999)    except InsufficientFundsError as e:        print(f”❌ {e}”)# 账户合并    print(”\n--- 账户合并测试 ---”)    merged = acc3 + BankAccount(”赵六”, ”BA002”, 3000)    print(f”合并账户:{merged}”)# 银行汇总    print(”\n--- 银行汇总 ---”)    print(bank)    print(f”账号 SA001 在银行?{'SA001' in bank}”)    print(f”账号 XX999 在银行?{'XX999' in bank}”)# 所有账户    print(”\n--- 所有账户 ---”)    for number, acc in bank._accounts.items():        print(f”  {number}: {acc}”)if __name__ == ”__main__”:    main()

输出结果:

=======================================================  拙见python 银行账户系统演示=======================================================🏦 拙见银行 开户成功:储蓄账户[SA001] 张三 余额:¥10000.00(利率3.5%)🏦 拙见银行 开户成功:信用账户[CA001] 李四 余额:¥2000.00(额度5000)🏦 拙见银行 开户成功:账户[BA001] 王五 余额:¥5000.00总账户数:3--- 存取款测试 ---张三存款 5000 后:¥15000.00王五取款 1000 后:¥4000.00张三取款 1000(含1元手续费)后:¥13999.00--- 信用账户透支测试 ---李四透支取款 6000 后:¥-4000.00李四可用额度:¥1000.00❌ 超出信用额度:可用 1000,尝试取 5000--- 利息测试 ---张三利息到账:¥489.97,新余额:¥14488.97--- 转账测试 ---转账结果:王五 → 张三:¥500.00王五余额:¥3500.00张三余额:¥14988.97--- 异常测试 ---❌ 存款金额必须大于 0,收到 -100❌ 余额不足:余额 3500,尝试取 99999--- 账户合并测试 ---合并账户:账户[BA001+BA002] 王五&赵六 余额:¥6500.00--- 银行汇总 ---拙见银行(3 个账户,总存款 ¥14488.97)账号 SA001 在银行?True账号 XX999 在银行?False--- 所有账户 ---  SA001: 储蓄账户[SA001] 张三 余额:¥14488.97(利率3.5%)  CA001: 信用账户[CA001] 李四 余额:¥-4000.00(额度5000)  BA001: 账户[BA001] 王五 余额:¥3500.00

思路解析:

    这道题是 OOP 系列的综合检验,覆盖了第 2 篇的全部知识点:

知识点
代码体现
类与对象
BankAccount
、SavingsAccount、CreditAccount、Bank
类属性 + 类方法
total_accounts
 + get_total() 记录总账户数
封装
__balance
 私有属性,外部只能通过 @property 只读访问
@propertybalance只读、account_number 只读
继承 + super()
SavingsAccount
/CreditAccount 继承 BankAccount
方法重写
两个子类各自重写 withdraw(),实现不同取款逻辑
多态
withdraw()
 在不同子类中有不同行为,调用方无需关心具体类型
魔术方法
__str__
、__repr__、__add__(合并账户)、__eq__、__len__、__contains__
自定义异常
InvalidAmountError
、InsufficientFundsError
组合优于继承
Bank
 内部持有账户字典,而非继承 BankAccount
名称重整
CreditAccount
 中通过 self._BankAccount__balance 访问父类私有属性

关键设计决策:

  • __balance用双下划线实现名称重整,子类不能直接访问 self.__balance,必须通过 super().withdraw() 或 self._BankAccount__balance(名称重整后的路径)。

  • CreditAccount 允许透支,所以不能直接调用 super().withdraw()(会抛异常),需要直接操作 _BankAccount__balance。

  • transfer() 通过调用 self.withdraw() + target.deposit() 实现——由于多态,如果是 SavingsAccount 转账,会自动走带手续费的 withdraw()。

  • __add__ 实现账户合并,返回新对象,不修改原有账户——不可变操作更安全。

    难度与知识点总结

    题号
    题目
    难度
    核心知识点
    1
    学生信息类
    ⭐
    类定义、__init__、self、实例方法
    2
    实例计数器
    ⭐⭐
    类属性 vs 实例属性、@classmethod
    3
    三种方法实战
    ⭐⭐
    实例方法、@classmethod、@staticmethod、工厂模式
    4
    带校验的密码类
    ⭐⭐⭐
    封装、私有属性、@property + @setter
    5
    动物王国继承体系
    ⭐⭐⭐
    单继承、super()、方法重写、isinstance()
    6
    多继承与 MRO 分析
    ⭐⭐⭐⭐
    多继承、C3 线性化、super() 在多继承中的行为
    7
    可运算的分数类
    ⭐⭐⭐⭐
    运算符重载、__eq__/__lt__、__str__/__repr__、__float__
    8
    Mixin 组合与鸭子类型
    ⭐⭐⭐⭐
    Mixin 模式、多继承组合、鸭子类型、__iter__/__len__
    9
    dataclass 购物车系统
    ⭐⭐⭐⭐⭐
    @dataclass
    、field()、组合优于继承、__contains__/__iter__
    10
    银行账户系统
    ⭐⭐⭐⭐⭐
    全部 OOP 知识点综合 + 自定义异常 + 名称重整

    写在最后

        10 道题,从最基础的类定义,到涵盖封装、继承、多态、魔术方法、dataclass 的银行系统,覆盖了第 2 篇面向对象的全部知识点。

    建议的练习方式:

    1. 先自己写——不看答案,能写多少写多少
    2. 卡住了回看文章——第 2 篇对应章节有完整示例
    3. 写完对比答案——思考有没有更 Pythonic 的写法
    4. 尝试扩展——给第 10 题加新功能(如定期账户、贷款账户、交易记录)

    如果你能独立做出第 10 题,说明 Python 面向对象已经过关了。

    关注「拙见python」,一起写干净的代码。


    下一篇预告:Python 模块与包—组织你的代码工程

    最新文章

    随机文章