完整代码如下:
class BankAccount:def __init__(self, account_holder, initial_balance=0):"""初始化银行账户:param account_holder: 账户持有人姓名:param initial_balance: 初始余额,默认为0"""self.account_holder = account_holderself.balance = initial_balanceself.transactions = [] # 记录交易历史def deposit(self, amount):"""存款操作:param amount: 存款金额""" if amount <= 0:print("存款金额必须大于0")returnself.balance += amountself.transactions.append(f"存款: +{amount:.2f}")print(f"成功存款 {amount:.2f}。当前余额: {self.balance:.2f}")def withdraw(self, amount):"""取款操作:param amount: 取款金额"""if amount <= 0:print("取款金额必须大于0")returnif amount > self.balance:print("余额不足,无法完成取款")returnself.balance -= amountself.transactions.append(f"取款: -{amount:.2f}")print(f"成功取款 {amount:.2f}。当前余额: {self.balance:.2f}")def get_balance(self):"""获取当前余额"""return self.balancedef get_transaction_history(self):"""获取交易历史记录"""return self.transactionsdef __str__(self):return f"账户持有人: {self.account_holder}, 当前余额: {self.balance:.2f}"if __name__ == "__main__":name = input("请输入账户持有人姓名: ")while True:try:initial_deposit = float(input("请输入初始存款金额: "))if initial_deposit < 0:print("初始存款金额不能为负数,请重新输入")continuebreakexcept ValueError:print("请输入有效的数字")# 创建账户account = BankAccount(name, initial_deposit)print(f"\n账户创建成功!")print(account)# 循环操作while True:print("\n请选择操作:")print("1. 存款")print("2. 取款")print("3. 查询余额")print("4. 查看交易记录")print("5. 退出") choice = input("请输入选项(1-5): ")if choice == "1":# 存款while True:try:amount = float(input("请输入存款金额: "))if amount <= 0:print("存款金额必须大于0")continueaccount.deposit(amount)breakexcept ValueError:print("请输入有效的数字")elif choice == "2":# 取款while True:try:amount = float(input("请输入取款金额: "))if amount <= 0:print("取款金额必须大于0")continueaccount.withdraw(amount)breakexcept ValueError:print("请输入有效的数字")elif choice == "3":# 查询余额print(f"\n当前余额: {account.get_balance():.2f}")elif choice == "4":# 查看交易记录print("\n交易记录:") for transaction in account.get_transaction_history():print(transaction)if not account.get_transaction_history():print("暂无交易记录")elif choice == "5":# 退出print("感谢使用银行账户系统,再见!")breakelse:print("无效的选项,请重新输入")
一、系统框架
-
类结构:
- 核心类
BankAccount
封装了银行账户的所有功能 - 使用面向对象编程(OOP)思想,将数据和操作封装在一起
- 核心类
-
主要组件:
class BankAccount:# 1. 初始化方法def __init__(self, account_holder, initial_balance=0)# 2. 存款功能def deposit(self, amount)# 3. 取款功能 def withdraw(self, amount)# 4. 查询功能def get_balance(self)def get_transaction_history(self)# 5. 字符串表示def __str__(self)
-
用户交互:
- 主程序通过
if __name__ == "__main__":
实现交互式命令行界面 - 使用 while 循环保持程序持续运行
- 主程序通过
二、设计思路
-
数据建模:
- 账户属性:持有人姓名、余额、交易记录
- 交易记录使用列表存储,记录每次操作的详细信息
-
功能实现原则:
- 单一职责原则:每个方法只做一件事
- 输入验证:对所有用户输入进行有效性检查
- 事务记录:自动记录每笔交易的详细信息
- 防错设计:处理负数金额、余额不足等异常情况
-
交互流程:
开始 → 输入账户信息 → 主菜单 → 选择操作 → 执行功能 → 返回主菜单 → 退出
-
异常处理:
- 使用 try-except 处理非数字输入
- 使用条件判断处理业务逻辑错误(如余额不足)
三、关键设计点
-
状态管理:
balance
变量实时跟踪账户余额transactions
列表完整记录所有交易历史
-
用户友好性:
- 清晰的提示信息
- 格式化的金额显示(保留两位小数)
- 操作结果的即时反馈
-
扩展性考虑:
- 通过类的方式实现,便于后续添加新功能
- 交易记录采用标准格式,便于扩展分析功能
四、典型工作流程示例
- 用户输入姓名和初始金额
- 系统创建账户对象
- 用户选择存款:
- 输入存款金额
- 系统验证并执行存款
- 更新余额和交易记录
- 用户选择查询:
- 系统显示当前余额
- 用户退出系统
五、代码分析
整体结构分析
该代码定义了一个BankAccount
类来模拟银行账户的基本操作,包括初始化账户、存款、取款、查询余额、查看交易记录等功能。在if __name__ == "__main__"
模块中,实现了与用户的交互,让用户能够创建账户并进行各种操作。
类定义分析
__init__
方法
- 功能:初始化银行账户对象。
- 接受
account_holder
(账户持有人姓名)和initial_balance
(初始余额,默认为0)两个参数。 - 初始化了三个实例变量:
self.account_holder
(存储账户持有人姓名)、self.balance
(存储账户余额)、self.transactions
(存储交易历史记录的空列表)。
- 接受
- 优点:通过参数初始化实例变量,使每个账户对象在创建时就有明确的初始状态。
deposit
方法
- 功能:实现存款操作。
- 检查存款金额
amount
是否小于等于0,若为真,打印提示信息并返回,不进行存款操作。 - 否则,将存款金额加到账户余额
self.balance
上,并将存款交易记录(格式为"存款: +{amount:.2f}"
)添加到self.transactions
列表中,同时打印存款成功信息和当前余额。
- 检查存款金额
- 优点:有输入校验(金额有效性检查),保证了存款操作的合理性,并且及时更新账户余额和记录交易历史。
withdraw
方法
- 功能:实现取款操作。
- 先检查取款金额
amount
是否小于等于0,若是,打印提示信息并返回。 - 再检查取款金额是否大于当前账户余额
self.balance
,若大于,打印余额不足提示信息并返回。 - 否则,从账户余额中减去取款金额,并将取款交易记录(格式为
"取款: -{amount:.2f}"
)添加到self.transactions
列表中,同时打印取款成功信息和当前余额。
- 先检查取款金额
- 优点:具备完善的输入校验(金额有效性和余额充足性检查),确保取款操作符合实际业务逻辑,保护账户资金安全。
get_balance
方法
- 功能:简单返回当前账户的余额
self.balance
。 - 优点:提供了一种便捷的方式获取账户余额,符合面向对象编程中封装的思想(外部通过方法访问内部数据)。
get_transaction_history
方法
- 功能:返回存储交易历史记录的
self.transactions
列表。 - 优点:方便用户查看账户的交易明细,是对账户操作历史的记录和展示。
__str__
方法
- 功能:定义了对象的字符串表示形式。当使用
print()
函数打印账户对象时,会自动调用该方法,返回一个包含账户持有人姓名和当前余额的字符串(格式为"账户持有人: {self.account_holder}, 当前余额: {self.balance:.2f}"
)。 - 优点:使账户对象在打印时能以友好、易读的方式呈现信息,方便用户直观了解账户基本情况,也有利于调试和日志记录等场景。
if __name__ == "__main__"
模块分析
- 获取用户输入并创建账户:
- 使用
input()
函数获取用户输入的账户持有人姓名name
。 - 通过循环和
try-except
块,确保用户输入的初始存款金额initial_deposit
是有效的数字且不为负数,然后创建BankAccount
对象account
。 - 打印账户创建成功信息和账户对象(调用
__str__
方法)。
- 使用
- 用户操作循环:
- 进入一个无限循环,展示操作菜单(存款、取款、查询余额、查看交易记录、退出)。
- 根据用户输入的选项
choice
,执行相应的操作(调用BankAccount
类的对应方法)。 - 对于每个操作(如存款、取款),同样使用循环和
try-except
块确保用户输入的金额是有效的数字,并进行相应的业务逻辑处理(调用类方法)。 - 若用户选择退出(选项
5
),打印感谢信息并终止循环,结束程序。
代码优点总结
- 面向对象封装:将账户相关的数据(余额、交易记录、持有人姓名等)和操作(存款、取款等)封装在
BankAccount
类中,体现了良好的面向对象设计思想,使代码结构清晰,便于维护和扩展。 - 输入校验完善:在存款、取款等操作中,对用户输入的金额进行了充分的有效性检查(如金额是否为正数、取款时余额是否足够等),增强了程序的健壮性,避免了不合理操作导致的错误。
- 交互友好:通过清晰的菜单提示和操作反馈(如打印存款、取款成功信息和当前余额等),使用户能够方便、直观地与程序进行交互,了解操作结果。
六、__init__
和__str__
是两个特殊的魔法方法
1. __init__
方法
核心作用:
- 构造函数:在创建类实例时自动调用
- 初始化对象:设置对象的初始状态
银行账户示例:
class BankAccount:def __init__(self, account_holder, initial_balance=0):self.account_holder = account_holder # 设置账户持有人self.balance = initial_balance # 设置初始余额self.transactions = [] # 初始化交易记录
关键特点:
- 第一个参数必须是
self
(指向新创建的对象) - 在实例化时自动执行:
account = BankAccount("张三", 1000) # 自动调用__init__
- 不能有返回值(隐式返回
None
)
2. __str__
方法
核心作用:
- 定义对象的字符串表示:当使用
print()
或str()
时自动调用 - 提供用户友好描述:便于调试和输出
银行账户示例:
def __str__(self):return f"账户持有人: {self.account_holder}, 当前余额: {self.balance:.2f}"
使用场景:
account = BankAccount("李四", 500)
print(account) # 自动调用__str__,输出:账户持有人: 李四, 当前余额: 500.00
关键特点:
- 必须返回字符串类型
- 如果没有定义
__str__
,Python会使用__repr__
作为备用 - 常用于日志、用户界面等需要可读输出的场景
3. 对比总结
特性 | __init__ | __str__ |
---|---|---|
调用时机 | 创建实例时 | 调用print() 或str() 时 |
主要用途 | 初始化对象属性 | 定义对象的可读字符串表示 |
返回值 | 不应返回任何值 | 必须返回字符串 |
是否必需 | 非必需(但绝大多数类都会实现) | 非必需(但建议重要类都实现) |
示例调用场景 | obj = ClassName(args) | print(obj) 或 str(obj) |
4. 实际应用关系
在银行账户系统中,这两个方法协同工作:
# 创建账户(调用__init__)
account = BankAccount("王五", 2000) # 查看账户信息(调用__str__)
print(account)
# 输出:账户持有人: 王五, 当前余额: 2000.00
5. 为什么需要它们?
-
__init__
: 确保对象创建后即处于有效状态,避免后续出现属性未定义的错误 -
__str__
: 提供比默认内存地址(如<__main__.BankAccount object at 0x...>
)更有意义的输出
6. 进阶提示
-
类似方法
__repr__
:用于开发者调试,应返回明确的创建表达式
def __repr__(self):return f'BankAccount("{self.account_holder}", {self.balance})'
-
在银行账户系统中,良好的
__str__
实现可以帮助:- 快速查看账户状态
- 生成对账单摘要
- 调试交易记录
七、__str__
方法的语言规范
-
必须返回字符串 Python明确规定
__str__
方法必须返回一个字符串对象(str
类型)。当调用print(obj)
或str(obj)
时,解释器会自动执行这个方法并显示其返回值。 -
与
print()
的协作机制
print(account) # 内部实际执行:# 1. 调用account.__str__()# 2. 打印该方法返回的字符串
3.银行账户示例解析
def __str__(self):return f"账户持有人: {self.account_holder}, 当前余额: {self.balance:.2f}"
-
返回值内容: 返回一个格式化的字符串,包含账户持有人和带两位小数的余额 (例如:
"账户持有人: 张三, 当前余额: 1000.00"
) -
技术细节:
f"..."
:f-string格式化字符串(Python 3.6+):.2f
:将浮点数格式化为2位小数
4.如果不返回字符串会发生什么?
❌ 错误示例:
def __str__(self):print("账户信息...") # 错误!__str__不应该直接打印
- 调用
print(account)
会导致:TypeError: __str__ returned non-string (type NoneType)
✅ 正确做法: 必须通过return
返回字符串,让调用者决定如何处理这个字符串。
5.为什么不是直接打印?
这种设计体现了关注点分离原则:
-
__str__
的职责: 只负责生成对象的字符串表示形式 (不关心这个字符串是用来打印、记录日志还是其他用途) -
调用方的自由: 返回字符串后,调用者可以:
# 选择1:直接打印
print(account)# 选择2:存入文件
with open("log.txt", "a") as f:f.write(str(account))# 选择3:拼接其他字符串
msg = "账户状态:" + str(account)
6.类比其他语言
语言 | 类似机制 |
---|---|
Java | toString() 方法 |
C# | ToString() 方法 |
JavaScript | toString() 原型方法 |
C++ | 重载 << 操作符 |
八、while True
语句、 if elif else
语句和 try except
语句的详细介绍
while True
语句
- 作用:创建一个无限循环,只要条件
True
始终满足(实际上就是一直满足),循环体就会不断执行。通常需要在循环内部通过break
语句来终止循环。 - 语法结构:
while True:# 循环体代码if 终止条件:break
- 示例:
while True:user_input = input("请输入一个数字(输入 'q' 退出): ")if user_input == 'q':breaktry:num = int(user_input)print(f"你输入的数字是: {num}")except ValueError:print("输入无效,请输入一个整数。")
- 应用场景:
- 创建交互式程序,如命令行工具,等待用户持续输入指令,直到用户发出退出信号(如上述示例)。
- 需要持续监听某种状态(如服务器监听客户端连接请求)的场景。
if elif else
语句
- 作用:用于条件判断,根据不同的条件执行相应的代码块。
if
后面可以跟多个elif
(表示“否则如果”),最后可以有一个可选的else
(表示前面条件都不满足时执行)。 - 语法结构:
if 条件1:# 条件1满足时执行的代码
elif 条件2:# 条件2满足时执行的代码
else:# 前面条件都不满足时执行的代码
- 示例:
score = 85
if score >= 90:print("优秀")
elif score >= 80:print("良好")
elif score >= 60:print("及格")
else:print("不及格")
- 应用场景:
- 根据不同的数值范围、逻辑条件执行不同操作,如成绩等级判断(如上例)。
- 处理不同用户角色的权限逻辑(如判断用户是管理员、普通用户等,执行不同的功能)。
try except
语句
- 作用:用于捕获和处理程序运行时可能出现的异常,防止程序因为异常而崩溃。
try
块中放置可能引发异常的代码,except
块用于处理捕获到的异常。 - 语法结构:
try:# 可能引发异常的代码
except 异常类型:# 处理异常的代码
- 示例:
try:result = 10 / 0 # 会引发 ZeroDivisionError 异常
except ZeroDivisionError:print("除数不能为零!")
- 场景:
- 处理用户输入可能不符合预期的情况(如尝试将用户输入转换为特定类型时,用户输入了非数字字符)。
- 进行文件操作、网络请求等可能失败的操作时,捕获并处理相应的异常(如文件不存在
FileNotFoundError
、网络连接超时等)。
在实际编程中,这三种语句经常结合使用。例如在银行账户系统代码里:
while True:try:initial_deposit = float(input("请输入初始存款金额: "))if initial_deposit < 0:print("初始存款金额不能为负数,请重新输入")continuebreakexcept ValueError:print("请输入有效的数字")
这里
while True
创建了一个循环让用户不断输入,直到输入有效(通过break
终止循环)。try except
用于处理用户输入不是数字的情况(捕获ValueError
异常),if
语句用于进一步校验输入数字的合理性(是否为负数)。
再比如在取款操作的代码中:
elif choice == "2":# 取款while True:try:amount = float(input("请输入取款金额: "))if amount <= 0:print("取款金额必须大于0")continueaccount.withdraw(amount)breakexcept ValueError:print("请输入有效的数字")
九、break
和 continue
是用于控制循环流程的关键字
作用和功能
break
- 作用:直接终止当前所在的循环(可以是
for
循环或while
循环),循环体中break
语句之后的代码将不再执行,程序会跳出循环,继续执行循环后面的代码。 - 示例:
- 作用:直接终止当前所在的循环(可以是
for i in range(10):if i == 5:breakprint(i)
# 输出结果为 0 1 2 3 4,当 i 等于 5 时,循环终止
continue
- 作用:跳过本次循环中
continue
语句之后的代码,直接进入下一次循环的条件判断(对于for
循环,会继续迭代下一个元素;对于while
循环,会继续判断循环条件)。 - 示例:
- 作用:跳过本次循环中
for i in range(10):if i % 2 == 0:continueprint(i)
# 输出结果为 1 3 5 7 9,当 i 是偶数时,跳过本次循环的打印操作,直接进入下一次循环
适用场景
break
- 当你在循环中发现某个特定条件满足,并且后续不需要再执行循环中的任何操作时,就可以使用
break
来提前结束循环。例如在一个列表查找元素的循环中,一旦找到目标元素,就可以用break
停止继续查找。 - 示例:
- 当你在循环中发现某个特定条件满足,并且后续不需要再执行循环中的任何操作时,就可以使用
my_list = [10, 20, 30, 40, 50]
target = 30
for num in my_list:if num == target:print(f"找到目标元素 {target}")break
continue
- 当你希望在满足某些条件时,跳过本次循环的一部分操作,但循环还需要继续执行下去时,适合使用
continue
。比如在遍历一个数字列表时,想忽略其中的偶数,只对奇数进行某些计算或处理。 - 示例
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for num in numbers:if num % 2 == 0:continue # 跳过偶数# 以下是对奇数的处理逻辑print(f"处理奇数: {num}")
对循环嵌套的影响
break
:在循环嵌套的情况下,break
只会终止它所在的那一层循环。- 示例:
for i in range(5):for j in range(5):if j == 2:break # 只会跳出内层的 j 循环print(f"i={i}, j={j}")
# 输出结果是 当 j 等于 2 时,内层循环停止,外层循环继续
continue
:同样在循环嵌套中,continue
也只影响它所在的那一层循环,会跳过该层循环中continue
后面的代码,进入该层循环的下一次迭代。- 示例:
for i in range(5):for j in range(5):if j == 2:continue # 跳过内层 j 循环中 j=2 时后续的代码,继续 j 的下一次循环print(f"i={i}, j={j}")
# 输出结果是 当 j 等于 2 时,跳过本次 j 循环的打印操作,j 继续递增
总的来说,break
用于快速结束循环,continue
用于有条件地跳过循环体中的部分代码,它们为程序员提供了灵活控制循环流程的手段。