Python OOP实现银行系统时所有账户交易记录同步复制问题
问题现象
基于面向对象思想编写的Python银行管理系统,账户创建功能可正常运行,但执行存取款操作时存在数据串扰问题:对任意单个账户发起的交易记录,会同步出现在所有已创建账户的交易流水列表中。例如创建3个账户后仅向其中1个账户存入2000卢比,该笔存款记录会同时显示在另外2个账户的流水中,耗时5天排查未定位问题原因。
原始实现代码如下:
import datetime try: class Bank: bank = dict() bank_preset = 1000 def create_acc(self): return self.bank_preset + len(self.bank) + 1 def set_details(self,name,pin,mobile="None",transactions=list()): data_dict = { "name":name, "pin":pin, "balance":0, "transactions":transactions} return data_dict def get_details(self,acc_no,pin): for k,v in self.bank[acc_no].items(): print(k," -> ",v) def transaction_preset(self,tr_type, amount, date_time): tr_preset = { 'tr_type': tr_type, 'datetime': date_time, 'amount': amount, } return tr_preset def deposite(self,acc_no,amount): data = self.transaction_preset('cr',amount,datetime.datetime.now()) self.bank[acc_no]["transactions"].append(data) self.bank[acc_no]["balance"] += amount def withdraw(self,acc_no,amount): date = datetime.datetime.now() assert amount < self.bank[acc_no]["balance"],"Insufficient balance." data = self.transaction_preset('db',amount,datetime.datetime.now()) self.bank[acc_no]["transactions"].append(data) self.bank[acc_no]["balance"] -= amount def mini_statement(self,acc_no): print(" All transactions ".center(60, "-"), end="\n\n") print("".center(50, "_")) print("amount".center(15) + "cr/db".center(15) + "date & time".center(15)) print("".center(50, "_")) for tr_item in selfenter code here.bank[acc_no]['transactions']: for val in tr_item.values(): print(str(val).center(15), end="") print("") except Exception as err: print(err) o1 = Bank() acc_no = o1.create_acc() acc_data = o1.set_details("Kuldeep",2020) o1.bank.setdefault(acc_no,acc_data) o1.deposite(1001,10500) o1.bank o2 = Bank() acc_no = o2.create_acc() acc_data = o2.set_details("jigar",1010) o2.bank
根因定位
问题由两处Python语法特性的误用共同导致:
- 可变默认参数陷阱:
set_details方法中使用transactions=list()作为参数默认值。Python函数的默认参数在函数定义阶段完成初始化,而非每次调用函数时重新生成,所有未显式传入transactions参数的账户,都会共用同一个列表对象。此时向任意账户的交易列表追加数据,本质是修改这个全局共享的列表,所有引用该列表的账户自然都会看到这条交易记录。 - 类属性共享问题:将存储所有账户数据的
bank字典定义为类属性,所有Bank类的实例(如代码中的o1、o2)都会共享同一个类级别的bank字典,而非每个实例独立持有账户集合,进一步放大了数据串扰的问题。 - 附加代码错误:
mini_statement方法中存在残留输入selfenter code here.bank,属于拼写错误,运行时会直接抛出属性不存在的异常。
修复方案
按以下三点调整代码即可解决问题:
- 新增
__init__构造方法,将bank字典、bank_preset等属性从类属性改为实例属性,保证每个Bank实例独立持有自己的账户数据集 - 移除
set_details方法中的可变默认参数,将交易列表的初始化放到方法内部,每次创建新账户时生成独立的空交易列表 - 修正
mini_statement方法中的拼写错误
修复后的可运行代码:
import datetime class Bank: def __init__(self): # 实例属性,每个实例独立持有账户数据 self.bank = dict() self.bank_preset = 1000 def create_acc(self): return self.bank_preset + len(self.bank) + 1 def set_details(self, name, pin, mobile="None"): data_dict = { "name": name, "pin": pin, "mobile": mobile, "balance": 0, # 每次调用生成新的独立交易列表,避免共享 "transactions": list() } return data_dict def get_details(self, acc_no, pin): # 补充PIN校验逻辑 if self.bank[acc_no]["pin"] != pin: print("PIN verification failed") return for k, v in self.bank[acc_no].items(): print(k, " -> ", v) def transaction_preset(self, tr_type, amount, date_time): tr_preset = { 'tr_type': tr_type, 'datetime': date_time, 'amount': amount, } return tr_preset def deposite(self, acc_no, amount): data = self.transaction_preset('cr', amount, datetime.datetime.now()) self.bank[acc_no]["transactions"].append(data) self.bank[acc_no]["balance"] += amount def withdraw(self, acc_no, amount): if amount > self.bank[acc_no]["balance"]: raise ValueError("Insufficient balance.") data = self.transaction_preset('db', amount, datetime.datetime.now()) self.bank[acc_no]["transactions"].append(data) self.bank[acc_no]["balance"] -= amount def mini_statement(self, acc_no): print(" All transactions ".center(60, "-"), end="\n\n") print("".center(50, "_")) print("amount".center(15) + "cr/db".center(15) + "date & time".center(15)) print("".center(50, "_")) # 修正拼写错误 for tr_item in self.bank[acc_no]['transactions']: for val in tr_item.values(): print(str(val).center(15), end="") print("") # 测试逻辑 if __name__ == "__main__": o1 = Bank() acc_no1 = o1.create_acc() acc_data1 = o1.set_details("Kuldeep", 2020) o1.bank.setdefault(acc_no1, acc_data1) o1.deposite(1001, 10500) o2 = Bank() acc_no2 = o2.create_acc() acc_data2 = o2.set_details("jigar", 1010) o2.bank.setdefault(acc_no2, acc_data2) # 此时o1的账户流水仅自己可见,o2的交易列表为空,不会出现串扰 o1.mini_statement(1001) o2.mini_statement(1001)
注:Python中禁止使用列表、字典这类可变对象作为函数默认参数,否则极易出现本次遇到的数据共享问题,默认参数请尽量使用整数、字符串、None这类不可变值,需要默认空列表/字典时请在函数内部初始化。
内容的提问来源于stack exchange,提问作者Mevada Himal
相关产品推荐
相关产品推荐

