Python递归函数查找Excel最近匹配日期时触发递归深度超限
问题根因排查
两个核心问题共同导致了异常:
- 递归返回值缺失:
find_closest_date函数中,未匹配到当前日期的递归分支没有加return关键字。递归触达最内层找到匹配日期后,返回值无法逐层向上传递,外层函数只会返回None,逻辑完全断裂。 - 一次性生成器耗尽:openpyxl使用
read_only=True加载文件时,ws.rows是只能迭代一次的生成器。第一次调用find_date遍历完所有行后,生成器就被完全耗尽,后续递归调用传入的wsRows没有可迭代内容,永远匹配不到日期,最终无限递归触发栈溢出。
可行解决方案
优化版(推荐,性能与稳定性最优)
不再每次遍历工作表查找日期,提前遍历一次构建日期-行号映射字典,查找效率从O(n)降至O(1),同时彻底规避生成器耗尽问题,还避免了递归栈溢出风险。
import openpyxl from datetime import datetime, timedelta def preload_date_map(ws): # 提前遍历一次工作表,构建日期-行号映射字典 date_map = {} for row in ws.iter_rows(): for cell in row: # 只匹配datetime类型单元格,避免误匹配其他值 if isinstance(cell.value, datetime): # 同一行多个日期时保留第一个匹配的行号 date_map.setdefault(cell.value, cell.row) return date_map def find_closest_date(target_date, date_map, search_type, max_offset=365): # search_type=0 查找过去最近日期,1 查找未来最近日期 current_date = target_date offset_step = -1 if search_type == 0 else 1 # 增加最大查找范围限制,避免极端情况无限循环 for _ in range(max_offset): if current_date in date_map: return date_map[current_date] current_date += timedelta(days=offset_step) # 超出查找范围仍未匹配到则返回-1 return -1 if __name__ == "__main__": startDate = datetime.strptime('2021-01-18', '%Y-%m-%d') endDate = datetime.strptime('2021-02-16', '%Y-%m-%d') wb = openpyxl.load_workbook('C:\\PATH\\TO\\FILE.xlsx', read_only=True) ws = wb['SHEET'] # 提前加载日期映射表,仅遍历一次工作表 date_map = preload_date_map(ws) # 原有直接查找逻辑可直接通过字典取值完成 print(str(startDate) + " (" + str(type(startDate)) + ") " + str(date_map.get(startDate, -1))) print(str(endDate) + " (" + str(type(endDate)) + ") " + str(date_map.get(endDate, -1))) # 查找过去最近的日期,默认最多前后追溯365天,可自定义调整范围 print(find_closest_date(endDate, date_map, search_type=0)) wb.close()
最小改动版(保留原有代码结构)
如果不想改动原有函数逻辑,仅需两处调整即可运行:
- 递归分支补充
return关键字 - 将一次性生成器转为列表传入,避免迭代后耗尽
修改后的find_closest_date函数:
def find_closest_date(date, wsRows, searchType): result = find_date(date, wsRows) if result == -1: nextDate = date if searchType == 0: nextDate -= timedelta(days=1) else: nextDate += timedelta(days=1) # 补充return,将递归结果逐层向上传递 return find_closest_date(nextDate, wsRows, searchType) else: return result
调用时不要直接传ws.rows生成器,需转为列表:
print(find_closest_date(endDate, list(ws.rows), 0))
注意:该版本仍存在每次查找全表遍历的性能问题,数据量较大时运行速度会明显变慢,优先推荐使用预加载字典的优化版本。
内容的提问来源于stack exchange,提问作者Mariano
相关产品推荐
相关产品推荐

