Python 3中向空嵌套字典添加数据时的异常排查
问题
我是Python新手,在构建/操作网络图的过程中学习字典概念,但遇到了问题。我有如下格式的CSV文件:
RC11,RC12,RC13,RC14,RC15,RC16,RC17,RC18,RC19,RC110 RC21,RC22,RC23,RC24,RC25,RC26,RC27,RC28,RC29,RC210 RC31,RC32,RC33,RC34,RC35,RC36,RC37,RC38,RC39,RC310
我使用networkx包读取该文件创建MultiDiGraph,其中前两列代表边的两个节点,其余列构成存储在属性字典中的各种边属性。我尝试将这些值添加到预先初始化的名为moves的嵌套字典中:
from collections import defaultdict import pandas as pd import networkx as nx # 创建空的嵌套字典 movesTree = lambda: defaultdict(movesTree) moves = movesTree() # 用pandas读取CSV文件 columns = list(pd.read_csv(fileURI, sep=',', nrows=0).columns) # 仅获取表头 routingData = pd.read_csv(fileURI, sep=',').dropna() # 读取整个文件 # 向已创建的MultiDiGraph routingNetwork添加边 routingNetwork = nx.MultiDiGraph() for i, elrow in routingData.iterrows(): routingNetwork.add_edge(elrow[0], elrow[1], attr_dict=elrow[2:].to_dict()) # 将行中的值添加到字典moves # 前6个值构成唯一键 # 最后5个值对应该键的各种属性(来自表头列表) moves[elrow[0]][elrow[1]][elrow[2]][elrow[3]][elrow[4]][elrow[5]] = [(columns[6], elrow[6]), (columns[7], elrow[7]), (columns[8], elrow[8]), (columns[9], elrow[9]), (columns[10], elrow[10])] print(list(moves))
但print(list(moves))仅输出去重后的节点列表,且缺少终端节点。请问我哪里出错了?
注:实际上,每个节点的moves只需保存该节点的出边数据。在实现该逻辑前,我测试上述语句的语法和输出时遇到了瓶颈。
解决方法
问题原因
你使用的是递归式defaultdict,list(moves)只会返回最外层字典的键(也就是所有起始节点elrow[0]),这就是为什么你只能看到起始节点列表——终端节点是嵌套字典的第二层键,不会被list(moves)直接返回。
另外代码里还有两个明显问题:
- 索引越界:你的CSV每行只有10个元素(索引0~9),但代码里引用了
columns[10],会触发IndexError。 - 列数不匹配:你说“前6个值构成唯一键,最后5个值对应属性”,但6+5=11,和CSV实际的10列矛盾,需要确认CSV的实际列数。
优化方案
针对“每个节点保存出边数据”的需求,没必要用层级过深的嵌套字典,改用起始节点为键、出边信息列表为值的结构更清晰,后续访问和维护也更简单。
修改后的代码示例:
from collections import defaultdict import pandas as pd import networkx as nx # 初始化moves:键是起始节点,值是该节点的出边信息列表 moves = defaultdict(list) fileURI = "你的CSV文件路径" routingNetwork = nx.MultiDiGraph() # 读取CSV并获取表头 routingData = pd.read_csv(fileURI, sep=',').dropna() columns = routingData.columns.tolist() # 遍历数据行 for _, elrow in routingData.iterrows(): start_node = elrow[0] end_node = elrow[1] # 提取前6个值作为唯一键(索引0~5) key_values = elrow.iloc[0:6].tolist() # 提取属性:根据实际列数调整索引,这里假设是索引6~9(对应最后4列) attr_names = columns[6:10] attr_values = elrow.iloc[6:10].tolist() edge_attrs = dict(zip(attr_names, attr_values)) # 添加边到网络图 routingNetwork.add_edge(start_node, end_node, **edge_attrs) # 将出边信息存入moves moves[start_node].append({ "end_node": end_node, "unique_key": key_values, "attributes": edge_attrs }) # 打印所有节点的出边数据 for node, edges in moves.items(): print(f"节点 {node} 的出边:") for edge in edges: print(f" 目标节点:{edge['end_node']}") print(f" 唯一键:{edge['unique_key']}") print(f" 属性:{edge['attributes']}\n") # 若仅需查看所有起始节点 print("所有起始节点:", list(moves.keys()))
说明
- 新的
moves结构直接对应“每个节点保存出边数据”的需求,通过moves[起始节点]就能获取该节点的所有出边信息。 - 用
iloc明确指定索引范围,避免隐式索引的问题,同时解决了之前的索引越界错误。 - 若你的CSV实际是11列,只需将属性的索引范围改为
columns[6:11]和elrow.iloc[6:11]即可。
内容的提问来源于stack exchange,提问作者user1931485
相关产品推荐
相关产品推荐

