生成Account对象树时触发RecursionError的原因排查
问题描述
我正在开发一个程序,用于读取会计科目表并将其转换为Account对象树,后续将基于该树进行操作。会计科目表包含多层子科目,每个子科目的名称比其父科目多缩进3个空格,示例如下:
Current Assets Cash 1000000.00 - Cash 1000010.00 - Bank Account #1 ... Other Current Assets Receivables 100800.00 - Accounts Receivable ...
编写的Account类如下:
class Account: parent = None num: int | None name: str # Name after stripping whitespace and splitting off account number raw: str # Name prior to ^ ... # Assorted irrelevant class variables acct_type: str | None detail_type: str | None _header: bool = False # Flags whether an account's a header account descendants: list = list() # Holds account's children. Should be empty if _header == False. def __init__(self, instr: str, parent=None, acct_type: str = None, detail_type: str = None): ''' Initialize an Account. - parent: Account. `None` if top-level. - instr: raw string from ACS CoA. - acct_type: QBO Account Type - detail_type: QBO Detail Type ''' self.raw = instr self.parent = parent self.acct_type = acct_type self.detail_type = detail_type stripped = instr.strip() ... # Assorted initialization code, incl. manipulating stripped # to get values for self.num and self.name ... # Assorted irrelevant methods def is_header(self) -> bool: return self._header def depth(self) -> int: return (len(self.raw) - len(self.raw.strip())) / 3 # self.raw def add_descendant(self, instr, acct_t: str = None, detail: str = None): '''Add descendant to account. If `acct_type` and `detail_type` are supplied, will override parent account type. ''' if len(self.descendants) > 0: if calc_depth(instr) > self.descendants[-1].depth(): self.descendants[-1].add_descendant(instr, acct_t = acct_t, detail = detail) else: self.descendants.append(Account( instr, parent=self, acct_type = self.acct_type if acct_t is None else acct_t, detail_type = self.detail_type if detail is None else detail )) else: self.descendants.append(Account( instr, parent=self, acct_type = self.acct_type if acct_t is None else acct_t, detail_type = self.detail_type if detail is None else detail )) return if __name__ == "__main__": accounts: list[Account] = list() for (acs_name, account_type, detail_type) in get_data(): # Gets data as list[tuple[str, str, str]] d = len(acs_name) - len(acs_name.lstrip())) / 3 # Depth of current row's account if d == 0: # Overall header accounts.append(Account(acs_name, parent=None, acct_type=account_type, detail_type=detail_type)) else: if account_type is not None: # Account metadata supplied in CoA spreadsheet accounts[-1].add_descendant( acs_name, acct_t = account_type, detail = detail_type) else: # Impute account metadata from parent accounts[-1].add_descendant(acs_name)
运行时触发RecursionError,错误栈显示add_descendant方法无限递归。调试发现二级科目会不断将自身添加为后代,生成的树结构如下:
<__main__.Account object at 0x0000015631511BE0> # Current Assets <__main__.Account object at 0x0000015631796710> # Cash <__main__.Account object at 0x0000015631796710> # Cash <__main__.Account object at 0x0000015631796710> # Yet more Cash ... # 980-ish more Cash
预期的树结构应为:
<__main__.Account object at 0x0000015631511BE0> # Current Assets <__main__.Account object at 0x0000015631796710> # Cash <__main__.Account object at 0x0000015631XXXXXX> # 1000000.00 - Cash
问题分析与解决方案
核心错误:类变量与实例变量混淆
你在Account类中定义的parent、descendants等是类变量,所有Account实例会共享同一个descendants列表,而不是每个实例拥有独立的列表。这就导致当你向某个实例的descendants添加元素时,所有实例的descendants都会被修改,最终引发无限递归。
修复步骤:
- 将类变量改为实例变量:在
__init__方法中初始化parent、descendants等变量,确保每个实例拥有独立的属性:
class Account: # 移除类级别的parent、descendants定义 num: int | None name: str raw: str acct_type: str | None detail_type: str | None _header: bool = False def __init__(self, instr: str, parent=None, acct_type: str = None, detail_type: str = None): self.raw = instr self.parent = parent # 实例变量 self.acct_type = acct_type self.detail_type = detail_type self.descendants = [] # 每个实例独立的列表 stripped = instr.strip() ... # 原有初始化代码
- 修复
calc_depth未定义问题:代码中调用了calc_depth(instr)但未实现该函数,需要添加一个计算输入字符串深度的辅助函数,或者直接在add_descendant中计算:
def get_str_depth(instr: str) -> int: return (len(instr) - len(instr.strip())) // 3 # 用整数除法避免浮点 # 在add_descendant中替换calc_depth调用: def add_descendant(self, instr, acct_t: str = None, detail: str = None): current_depth = get_str_depth(instr) if len(self.descendants) > 0: last_child_depth = self.descendants[-1].depth() if current_depth > last_child_depth: self.descendants[-1].add_descendant(instr, acct_t, detail) else: self.descendants.append(Account( instr, parent=self, acct_type=self.acct_type if acct_t is None else acct_t, detail_type=self.detail_type if detail is None else detail )) else: self.descendants.append(Account( instr, parent=self, acct_type=self.acct_type if acct_t is None else acct_t, detail_type=self.detail_type if detail is None else detail ))
- 修正主逻辑中的深度计算:主循环中的深度计算多了一个右括号,需要修正:
d = (len(acs_name) - len(acs_name.lstrip())) // 3 # 修正括号,用整数除法
额外优化建议
- 维护当前节点路径:按顺序处理科目时,建议维护一个当前节点的栈(路径),根据当前深度调整栈的长度,这样比递归调用
add_descendant更直观,也避免递归深度问题:
if __name__ == "__main__": accounts: list[Account] = list() current_path = [] # 保存当前层级的节点栈 for (acs_name, account_type, detail_type) in get_data(): d = (len(acs_name) - len(acs_name.lstrip())) // 3 # 调整当前路径到对应深度 while len(current_path) > d: current_path.pop() if d == 0: new_acct = Account(acs_name, parent=None, acct_type=account_type, detail_type=detail_type) accounts.append(new_acct) current_path = [new_acct] else: parent_acct = current_path[-1] new_acct = Account( acs_name, parent=parent_acct, acct_type=account_type if account_type is not None else parent_acct.acct_type, detail_type=detail_type if detail_type is not None else parent_acct.detail_type ) parent_acct.descendants.append(new_acct) current_path.append(new_acct)
内容的提问来源于stack exchange,提问作者In Hoc Signo
相关产品推荐
相关产品推荐

