第7章 7.5 综合实战:银行账户管理系统

🎯 开场:为什么你的钱包需要「面向对象」?

上一章我们学会了用类方法来组织代码,还记得那个「会自我描述」的 __str__ 吗?那一章的结尾我留了个小悬念:如果把这些方法组合起来,能做一个真正能用的东西吗?

答案是:能,而且做一个银行账户系统,比你想象的简单得多。

你有没有遇到过这种情况——月底对着银行短信发愁:「我到底花了多少钱?还剩多少?转给朋友的钱到账了没?」这些需求听起来很复杂,但其实就是一个账户管理器该干的事。

今天我们就用 Python 的面向对象,把这些功能全部实现出来。学完这章,你会有一个真正能跑起来的银行账户管理系统,而且还能加利息、算年费、做对账——这些功能下一章讲 collections 的时候会用到,到时候你会发现数据结构选对了,事半功倍。


🧱 基础:银行账户的「类」设计

什么是银行账户?生活类比

想象你去银行开户。柜员会让你填一张表,这张表上有什么?

  • 账号(唯一标识,相当于你的身份证号)
  • 户名(你的名字)
  • 余额(你存了多少钱)
  • 开户时间(什么时候开的户)

每次你存款,余额就增加;取款,余额就减少;转账,就是你少了他多了。这些动作,在代码里就是方法

说白了:一个银行账户 = 数据(账号、余额)+ 行为(存钱、取钱、转账)

这不就是我们一直在学的「类」吗?

第一个账户类

先来看一个最简单版本:

class BankAccount:
"""银行账户类"""

def __init__(self, account_id, owner_name, initial_balance=0):
    """开户:创建账户时需要账号、户名、初始存款"""
    self.account_id = account_id      # 账号
    self.owner_name = owner_name      # 户名
    self.balance = initial_balance    # 余额
    self.transaction_history = []     # 交易记录
    print(f"开户成功!账号 {account_id},户名 {owner_name},存入 {initial_balance} 元")

def deposit(self, amount):
    """存款:余额增加"""
    if amount <= 0:
        print("存款金额必须大于0")
        return False
    self.balance += amount
    self.transaction_history.append(f"存款 +{amount}")
    print(f"存款成功!当前余额:{self.balance} 元")
    return True

def withdraw(self, amount):
    """取款:余额减少"""
    if amount <= 0:
        print("取款金额必须大于0")
        return False
    if amount > self.balance:
        print(f"余额不足!当前余额:{self.balance} 元")
        return False
    self.balance -= amount
    self.transaction_history.append(f"取款 -{amount}")
    print(f"取款成功!当前余额:{self.balance} 元")
    return True

def get_balance(self):
    """查询余额"""
    return self.balance

def __str__(self):
    """打印账户信息"""
    return f"账号:{self.account_id},户名:{self.owner_name},余额:{self.balance} 元"

# 创建一个账户
account = BankAccount("001", "张三", 1000)
print(account)

输出:

开户成功!账号 001,户名 张三,存入 1000 元
账号:001,户名:张三,余额:1000 元

这段代码做了啥?
- __init__ 就是「开户」动作,创建账户时自动执行
- depositwithdraw 是存钱取钱,存钱加余额,取钱减余额
- get_balance 返回当前余额
- transaction_history 记录了每一笔交易,像银行的对账单

为什么要用魔术方法 __str__

看最后两行代码:

print(account)        # 用 print 打印对象

如果没有 __str__,print 会输出类似 <__main__.BankAccount object at 0x...> 这种看不懂的东西。有了 __str__,打印出来就是人类能读懂的信息。

类比:这就像银行的存折封面,有了它你一眼就知道「这是我的账户」而不是「这是一个银行对象」。

配图1 - 配图1

再加一个转账功能

两个人之间转钱,代码怎么写?

def transfer(self, target_account, amount):
    """转账:从本账户转钱到目标账户"""
    if amount <= 0:
        print("转账金额必须大于0")
        return False
    if amount > self.balance:
        print(f"余额不足!当前余额:{self.balance} 元")
        return False

    # 本账户扣钱
    self.balance -= amount
    self.transaction_history.append(f"转出 -{amount} 至 {target_account.account_id}")

    # 目标账户加钱
    target_account.balance += amount
    target_account.transaction_history.append(f"转入 +{amount} 来自 {self.account_id}")

    print(f"转账成功!向 {target_account.owner_name} 转账 {amount} 元")
    return True

这段代码的关键点:
- 转账是两个账户之间的事,所以需要 target_account 参数(另一个 BankAccount 对象)
- 本账户余额减少,目标账户余额增加,这两件事必须同时发生
- 双方都记录交易历史,方便对账


🔥 实战:三个项目递进做

项目 1:5 分钟搞定基础账户操作

跟着抄,就能跑:

class BankAccount:
def __init__(self, account_id, owner_name, initial_balance=0):
    self.account_id = account_id
    self.owner_name = owner_name
    self.balance = initial_balance
    self.transaction_history = []
    print(f"开户成功!账号 {account_id},户名 {owner_name},存入 {initial_balance} 元")

def deposit(self, amount):
    if amount <= 0:
        print("存款金额必须大于0")
        return False
    self.balance += amount
    self.transaction_history.append(f"存款 +{amount}")
    print(f"存款成功!当前余额:{self.balance} 元")
    return True

def withdraw(self, amount):
    if amount <= 0:
        print("取款金额必须大于0")
        return False
    if amount > self.balance:
        print(f"余额不足!当前余额:{self.balance} 元")
        return False
    self.balance -= amount
    self.transaction_history.append(f"取款 -{amount}")
    print(f"取款成功!当前余额:{self.balance} 元")
    return True

def transfer(self, target_account, amount):
    if amount <= 0:
        print("转账金额必须大于0")
        return False
    if amount > self.balance:
        print(f"余额不足!当前余额:{self.balance} 元")
        return False
    self.balance -= amount
    self.transaction_history.append(f"转出 -{amount} 至 {target_account.account_id}")
    target_account.balance += amount
    target_account.transaction_history.append(f"转入 +{amount} 来自 {self.account_id}")
    print(f"转账成功!向 {target_account.owner_name} 转账 {amount} 元")
    return True

def get_history(self):
    """查看交易历史"""
    return self.transaction_history

def __str__(self):
    return f"账号:{self.account_id},户名:{self.owner_name},余额:{self.balance} 元"

# 小明的账户
xiaoming = BankAccount("001", "小明", 5000)
print(xiaoming)

# 小红开户
xiaohong = BankAccount("002", "小红", 2000)
print(xiaohong)

# 小明存款1000
xiaoming.deposit(1000)

# 小红取款500
xiaohong.withdraw(500)

# 小明转账2000给小红
xiaoming.transfer(xiaohong, 2000)

# 查看交易历史
print(f"\n小明的交易记录:{xiaoming.get_history()}")
print(f"小红的交易记录:{xiaohong.get_history()}")

print(f"\n最终余额:小明 {xiaoming.balance} 元,小红 {xiaohong.balance} 元")

输出:

开户成功!账号 001,户名 小明,存入 5000 元
账号:001,户名:小明,余额:5000 元
开户成功!账号 002,户名 小红,存入 2000 元
账号:002,户名:小红,余额:2000 元
存款成功!当前余额:6000 元
取款成功!当前余额:1500 元
转账成功!向 小红 转账 2000 元

小明的交易记录:['存款 +1000', '转出 -2000 至 002']
小红的交易记录:['取款 -500', '转入 +2000 来自 001']

最终余额:小明 4000 元,小红 3500 元

一句话解释:小明原来5000,存了1000变6000,转出2000给小红了,最后剩4000。小红原来2000,取了500变1500,收到2000变3500。

项目 2:加上利息和年费(15 分钟)

真实银行会干嘛?利息!存款给利息,年费!管理账户要收钱。

class BankAccount:
def __init__(self, account_id, owner_name, initial_balance=0, annual_fee=0):
    self.account_id = account_id
    self.owner_name = owner_name
    self.balance = initial_balance
    self.transaction_history = []
    self.annual_fee = annual_fee  # 年费
    self.interest_rate = 0.035    # 年利率 3.5%
    self.creation_date = "2024-01-01"  # 简化处理,实际应该用 datetime

def deposit(self, amount):
    if amount <= 0:
        print("存款金额必须大于0")
        return False
    self.balance += amount
    self.transaction_history.append(f"存款 +{amount}")
    print(f"存款成功!当前余额:{self.balance} 元")
    return True

def withdraw(self, amount):
    if amount <= 0:
        print("取款金额必须大于0")
        return False
    if amount > self.balance:
        print(f"余额不足!当前余额:{self.balance} 元")
        return False
    self.balance -= amount
    self.transaction_history.append(f"取款 -{amount}")
    print(f"取款成功!当前余额:{self.balance} 元")
    return True

def transfer(self, target_account, amount):
    if amount <= 0:
        print("转账金额必须大于0")
        return False
    if amount > self.balance:
        print(f"余额不足!当前余额:{self.balance} 元")
        return False
    self.balance -= amount
    self.transaction_history.append(f"转出 -{amount} 至 {target_account.account_id}")
    target_account.balance += amount
    target_account.transaction_history.append(f"转入 +{amount} 来自 {self.account_id}")
    print(f"转账成功!向 {target_account.owner_name} 转账 {amount} 元")
    return True

def calculate_interest(self, months):
    """计算利息:按月计算,简化版"""
    monthly_rate = self.interest_rate / 12
    interest = self.balance * monthly_rate * months
    print(f"存款 {self.balance} 元,利率 {self.interest_rate*100}%,{months} 个月利息:{interest:.2f} 元")
    return interest

def apply_interest(self, months):
    """发放利息:余额增加"""
    interest = self.calculate_interest(months)
    self.balance += interest
    self.transaction_history.append(f"利息 +{interest:.2f}")
    print(f"利息已发放!当前余额:{self.balance:.2f} 元")
    return interest

def charge_annual_fee(self):
    """扣除年费"""
    if self.annual_fee > 0:
        if self.balance >= self.annual_fee:
            self.balance -= self.annual_fee
            self.transaction_history.append(f"年费 -{self.annual_fee}")
            print(f"年费 {self.annual_fee} 元已扣除,当前余额:{self.balance} 元")
        else:
            print(f"余额不足,无法扣除年费 {self.annual_fee} 元")

def get_balance(self):
    return self.balance

def get_history(self):
    return self.transaction_history

def monthly_statement(self):
    """月度对账单"""
    print(f"\n===== {self.account_id} {self.owner_name} 对账单 =====")
    print(f"当前余额:{self.balance:.2f} 元")
    print(f"年利率:{self.interest_rate*100}%")
    print(f"年费:{self.annual_fee} 元")
    print("交易记录:")
    for record in self.transaction_history:
        print(f"  - {record}")
    print("=" * 35)

def __str__(self):
    return f"账号:{self.account_id},户名:{self.owner_name},余额:{self.balance:.2f} 元"

# 创建账户:小明存1万,小红存5千,小明要交年费
xiaoming = BankAccount("001", "小明", 10000, annual_fee=100)
xiaohong = BankAccount("002", "小红", 5000, annual_fee=0)

print("\n--- 3个月后的操作 ---")
# 小明存了3个月,拿利息
xiaoming.apply_interest(3)
# 小红也存了3个月
xiaohong.apply_interest(3)

print("\n--- 扣除年费 ---")
xiaoming.charge_annual_fee()
xiaohong.charge_annual_fee()

print("\n--- 月度对账单 ---")
xiaoming.monthly_statement()
xiaohong.monthly_statement()

输出:

开户成功!账号 001,户名 小明,存入 10000 元
开户成功!账号 002,户名 小红,存入 5000 元

--- 3个月后的操作 ---
存款 10000 元,利率 3.5%,3 个月利息:87.50 元
利息已发放!当前余额:10087.50 元
存款 5000 元,利率 3.5%,3 个月利息:43.75 元
利息已发放!当前余额:5043.75 元

--- 扣除年费 ---
年费 100 元已扣除,当前余额:9987.50 元
余额不足,无法扣除年费 0 元

===== 001 小明 对账单 =====
当前余额:9987.50 元
年利率:3.5%
年费:100 元
交易记录:
- 利息 +87.50
- 年费 -100
===================================

===== 002 小红 对账单 =====
当前余额:5043.75 元
年利率:3.5%
年费:0 元
交易记录:
- 利息 +43.75
===================================

一句话解释:小明存了1万,3个月利息87.5元,扣掉100元年费,最后剩9987.5元。小红没年费,利息43.75元,最后5043.75元。

配图2 - 配图2

项目 3:做个银行系统管理器(15 分钟)

现在我们有很多账户了,需要一个银行系统来管理它们。想象你是银行柜员,手里有很多账户,要能:
- 开户
- 销户
- 查找账户
- 批量操作

class BankSystem:
"""银行系统:管理所有账户"""

def __init__(self, bank_name):
    self.bank_name = bank_name
    self.accounts = {}  # 用字典存储,key是账号,value是账户对象
    print(f"🏦 {bank_name} 系统初始化完成")

def open_account(self, account_id, owner_name, initial_balance=0, annual_fee=0):
    """开户"""
    if account_id in self.accounts:
        print(f"账号 {account_id} 已存在!")
        return None
    account = BankAccount(account_id, owner_name, initial_balance, annual_fee)
    self.accounts[account_id] = account
    print(f"✅ 开户成功!当前账户数:{len(self.accounts)}")
    return account

def close_account(self, account_id):
    """销户"""
    if account_id not in self.accounts:
        print(f"账号 {account_id} 不存在!")
        return False
    account = self.accounts[account_id]
    print(f"⚠️  销户警告:账号 {account_id}({account.owner_name})即将注销")
    print(f"   最终余额:{account.balance} 元")
    del self.accounts[account_id]
    print(f"✅ 销户成功!当前账户数:{len(self.accounts)}")
    return True

def find_account(self, account_id):
    """查找账户"""
    return self.accounts.get(account_id, None)

def show_all_accounts(self):
    """显示所有账户"""
    print(f"\n🏦 {self.bank_name} - 所有账户")
    print("=" * 40)
    for account in self.accounts.values():
        print(account)
    print(f"共 {len(self.accounts)} 个账户")
    print("=" * 40)

def get_total_deposits(self):
    """计算银行总存款"""
    total = sum(account.balance for account in self.accounts.values())
    print(f"📊 银行总存款:{total:.2f} 元,共 {len(self.accounts)} 个账户")
    return total

def batch_interest(self, months):
    """批量发放利息"""
    print(f"\n📋 批量发放 {months} 个月利息")
    for account in self.accounts.values():
        account.apply_interest(months)

def transfer_between_accounts(self, from_id, to_id, amount):
    """行内转账"""
    from_account = self.find_account(from_id)
    to_account = self.find_account(to_id)

    if not from_account:
        print(f"转出账号 {from_id} 不存在!")
        return False
    if not to_account:
        print(f"转入账号 {to_id} 不存在!")
        return False

    return from_account.transfer(to_account, amount)

# 创建一个银行系统
icbc = BankSystem("宇宙银行")

print("\n=== 开户 ===")
icbc.open_account("001", "小明", 10000, annual_fee=100)
icbc.open_account("002", "小红", 5000)
icbc.open_account("003", "小刚", 20000, annual_fee=50)

print("\n=== 查看所有账户 ===")
icbc.show_all_accounts()

print("\n=== 行内转账 ===")
icbc.transfer_between_accounts("001", "002", 3000)

print("\n=== 查找账户 ===")
account = icbc.find_account("002")
if account:
print(f"找到:{account}")

print("\n=== 批量发利息 ===")
icbc.batch_interest(6)

print("\n=== 统计 ===")
icbc.get_total_deposits()

print("\n=== 销户 ===")
icbc.close_account("003")

print("\n=== 最终账户列表 ===")
icbc.show_all_accounts()

输出:

🏦 宇宙银行 系统初始化完成

=== 开户 ===
开户成功!账号 001,户名 小明,存入 10000 元
✅ 开户成功!当前账户数:1
开户成功!账号 002,户名 小红,存入 5000 元
✅ 开户成功!当前账户数:2
开户成功!账号 003,户名 小刚,存入 20000 元
✅ 开户成功!当前账户数:3

=== 查看所有账户 ===
🏦 宇宙银行 - 所有账户
========================================
账号:001,户名:小明,余额:10000.00 元
账号:002,户名:小红,余额:5000.00 元
账号:003,户名:小刚,余额:20000.00 元
共 3 个账户
========================================

=== 行内转账 ===
转账成功!向 小红 转账 3000 元

=== 查找账户 ===
找到:账号:002,户名:小红,余额:8000.00 元

=== 批量发利息 ===
📋 批量发放 6 个月利息
存款 7000.00 元,利率 3.5%,6 个月利息:122.50 元
利息已发放!当前余额:7122.50 元
存款 8000.00 元,利率 3.5%,6 个月利息:140.00 元
利息已发放!当前余额:8140.00 元

📊 银行总存款:35122.50 元,共 3 个账户

=== 销户 ===
⚠️  销户警告:账号 003(小刚)即将注销
终余额:20512.50 元
✅ 销户成功!当前账户数:2

=== 最终账户列表 ===
🏦 宇宙银行 - 所有账户
========================================
账号:001,户名:小明,余额:7122.50 元
账号:002,户名:小红,余额:8140.00 元
共 2 个账户
========================================

一句话解释:银行系统就像一个 Excel 表格,accounts 字典是表格内容,账号是行号,每个账户是表格里的一行数据。批量操作就是对这个表格的所有行做同样的事。


💪 进阶:新手最容易踩的坑

坑 1:余额为负数!

错误示例

xiaoming = BankAccount("001", "小明", 100)
xiaoming.withdraw(200)  # 余额只有100,取200
print(f"余额:{xiaoming.balance}")  # 余额变成 -100 了!

正确做法:加一个余额检查,像项目里的代码那样。

坑 2:转账时忘记更新两个账户

错误示例

def transfer(self, target_account, amount):
self.balance -= amount
# 忘记给 target_account 加钱了!
print("转账完成")

正确做法

def transfer(self, target_account, amount):
self.balance -= amount
target_account.balance += amount  # 两个账户都要更新

坑 3:年费扣除时机不对

如果账户余额只有50元年费100,扣完变负数了,银行会怎么做?

正确做法:先检查余额够不够,不够就不扣。

def charge_annual_fee(self):
if self.balance >= self.annual_fee:  # 先检查!
    self.balance -= self.annual_fee

坑 4:用可变对象做默认参数

危险示例

class BadExample:
def __init__(self, name, history=[]):  # 坑!默认参数是可变对象
    self.name = name
    self.history = history

正确做法

class GoodExample:
def __init__(self, name, history=None):
    self.name = name
    self.history = history if history is not None else []  # 用 None 做默认值

坑 5:忘记 self 是谁

错误示例

def deposit(self, amount):
balance += amount  # 忘了加 self.balance

正确做法

def deposit(self, amount):
self.balance += amount  # 记得加 self

调试技巧:加日志

代码出问题怎么办?加 print 大法:

def withdraw(self, amount):
print(f"[DEBUG] 取款前余额:{self.balance},取款金额:{amount}")
if amount > self.balance:
    print(f"[ERROR] 余额不足!")
    return False
self.balance -= amount
print(f"[DEBUG] 取款后余额:{self.balance}")
return True

✏️ 练习题

练习 1(2 分钟):开户就能用

  • 输入:运行代码创建账户,初始存款 8888 元
  • 预期输出:打印「开户成功!账号 001,户名 小明,存入 8888 元」
  • 提示:直接改 initial_balance 参数就行

练习 2(2 分钟):余额不足时取款

  • 输入:账户余额 100,取款 200
  • 预期输出:「余额不足!当前余额:100 元」
  • 提示:看看 withdraw 方法里的 if 判断

练习 3(3 分钟):计算半年利息

  • 输入:存款 10000 元,年利率 3.5%,存 6 个月
  • 预期输出:利息 175 元
  • 提示:用 calculate_interest(6) 方法

练习 4(5 分钟):改写转账方法

  • 输入:在转账成功后,增加一条打印「交易完成,流水号 XXX」
  • 预期输出:转账时能看到「交易完成」字样
  • 提示:在 transfer 方法里加一行 print

练习 5(5 分钟):分析报错

  • 输入:运行下面这段代码
account = BankAccount("001", "测试", 1000)
account.withdraw(500)
print(account.balance)
  • 预期输出:报错 AttributeError: 'BankAccount' object has no attribute 'balance'
  • 提示:检查 __init__ 方法里余额是怎么存的

作业:做一个「个人财务小管家」

需求描述
做一个命令行工具,管理你的个人财务。支持以下功能:

功能点
1. 记账:记录收入和支出,自动计算余额
2. 统计:显示本月收入总额、支出总额、结余
3. 导出:把交易记录导出成文本文件

加分项(选做):
1. 支持多账户管理(工资卡、零花钱卡)
2. 支持月份查询(查指定月份的花销)

验收标准
- 能跑起来
- 输入命令能显示正确结果
- 代码有中文注释

提交方式:评论区贴代码或 GitHub 链接


📚 总结

今天我们用银行账户这个生活场景,完整实践了面向对象的三大核心:

  1. 是蓝图——像建筑图纸,定义了账户有什么(数据)和能做什么(方法)
  2. 实例是产品——按图纸盖出来的房子,每个账户都是独立的
  3. 魔术方法是自动化——__init__ 自动开户,__str__ 自动打印信息

这些概念看似简单,但下一章的 collections 高级数据结构 会用到——到时候你会发现,银行系统里存的那些账户,用列表还是字典效率差很多,选对了数据结构,做什么都快。

你在生活中有没有需要「管理一堆东西」的场景? 比如收藏夹管理、购物车、联系人……评论区聊聊,下一章我们会用 collections 优化这些问题!


延伸学习资源

  • 📖 官方文档:https://docs.python.org/zh-cn/3/tutorial/classes.html
  • 📖 《Python编程:从入门到实践》第 9 章「类」
  • 🎬 B站小甲鱼《Python教程》第 38-42 讲
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。