NetworkX 1.X 到 2.0 迁移指南:视图/迭代器 API、属性访问与函数命名空间的全面升级
【免费下载链接】networkxNetwork Analysis in Python项目地址: https://gitcode.com/gh_mirrors/ne/networkx
本指南以仓库 doc/release/migration_guide_from_1.x_to_2.0.rst 为骨架,结合当前仓库源码整理而成。NetworkX 2.0 是一次破坏性的大版本升级:基础图类(Graph / DiGraph / MultiGraph / MultiDiGraph)的许多方法签名、返回类型与属性访问方式发生了根本性变化。阅读本文后,你将掌握 2.0 视图/迭代器 API 的正确用法、
G.node/G.edge的替代方案、set_node_attributes等函数的参数重排、以及如何编写一套同时兼容 1.X 与 2.0 的迁移代码。
一、2.0 的核心变化:从"容器返回"到"视图/迭代器报告"
随着 NetworkX 2.0 的发布,项目转向了view/iterator reporting API(视图/迭代器报告 API)。大量原本返回列表或字典的方法改为返回视图(view),视图的设计灵感来自 Python 的 dictionary views。具体而言:
- 原本返回容器的 API 现在返回视图(
NodeView、EdgeView、DegreeView); - 原本返回迭代器的 API(如
nodes_iter、edges_iter)则被直接移除; - 创建新图的方法(
G.subgraph/edge_subgraph/reverse/to_directed/to_undirected)在数据复制的深度上发生了改变,且多数新增了以视图(view)方式创建而非复制数据的选项; - 图属性
G.node与G.edge被移除,改用G.nodes[n]与G.edges[u, v]; - 自环相关方法以及
add_path/add_star/add_cycle从图方法迁移为 networkx 命名空间下的函数。
设计者预期这些变化会破坏部分旧代码,但刻意让它们在抛出异常的方式上失败(如TypeError: unhashable type: 'dict'),以便开发者能立刻意识到代码已被破坏,而不是静默产生错误结果。
在 networkx/classes/reportviews.py 的模块文档字符串中,可以完整看到视图类家族的定位:
- 视图是快速创建、只读、可多次迭代的容器,并且是"活的"(live)——它们实时反映图的变化;
- 节点与边视图支持类集合(set-like)的成员测试与集合运算;
- 支持映射式查询:
G.nodes[n]返回节点属性字典,G.edges[u, v]返回边属性字典; - 通过
.data(...)支持按数据过滤的迭代,通过list(...)/dict(...)转换为具体容器。
需要特别留意的是,视图在迭代期间不应修改底层图结构(与 Python dict 的约束一致),否则会引发运行时错误;视图本身则可以反复迭代。
二、NodeView:G.nodes取代G.node与nodes_iter
2.1 基本用法
G.nodes(出于向后兼容,G.nodes()也可用)现在返回一个类似字典的NodeView,而G.nodes_iter()已被移除:
>>> import networkx as nx >>> G = nx.complete_graph(5) >>> G.nodes # for backward compatibility G.nodes() works as well NodeView((0, 1, 2, 3, 4))可以直接迭代G.nodes(或G.nodes()):
>>> for node in G.nodes: ... print(node) 0 1 2 3 4如果需要一个节点列表,使用 Python 内置的list函数:
>>> list(G.nodes) [0, 1, 2, 3, 4]2.2 集合运算与字典式查询
G.nodes具有集合特性,可以参与集合运算;同时它又是字典式的,可以用G.nodes[n]['weight']查询节点数据:
>>> H = nx.Graph() >>> H.add_nodes_from([1, 'networkx', '2.0']) >>> G.nodes & H.nodes # finding common nodes in 2 graphs {1} >>> # union of nodes in 2 graphs >>> G.nodes | H.nodes {0, 1, 2, 3, 4, 'networkx', '2.0'}节点数据则可以通过调用接口或数据视图访问。除了字典式视图自带的keys/values/items,G.nodes还提供数据视图G.nodes.data('weight'):
>>> G.add_node(1, time="5pm") >>> list(G.nodes(data=True)) [(0, {}), (1, {'time': '5pm'}), (2, {}), (3, {}), (4, {})] >>> list(G.nodes.data("time", default="Not Available")) [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available'), (3, 'Not Available'), (4, 'Not Available')]源码层面,nodes在 networkx/classes/graph.py 中被实现为@cached_property,返回NodeView;调用形态G.nodes(data=...)则产生NodeDataView(迭代(node, data)对,且不提供集合运算)。NodeDataView的详细行为定义在 networkx/classes/reportviews.py:使用data=True时数据视图携带完整属性字典(可写),使用data='color'时仅携带单个属性值,default=参数可为缺失属性提供默认值。
2.3G.node的去向
图属性G.node已被移除,其功能完全转移到G.nodes:
- 查询节点属性:
G.node[n]→G.nodes[n]; - 遍历节点属性对:
G.node.items()→G.nodes(data=True); - 仅遍历节点:
for n in G.node:→for n in G:(对图对象直接迭代节点,见 networkx/classes/graph.py 的__iter__)。
需要说明的是,为了帮助旧代码平滑过渡,v2.x 中保留了G.node作为指向G.nodes的过渡属性(transition pointer),项目曾计划在 v3.x 中将其彻底移除——升级到 2.0 后应尽快改用G.nodes。
2.4 危险的属性字典直赋操作
将节点属性字典直接从一张图复制到另一张图,可能破坏节点数据结构。下列写法在 v1.x 中"碰巧能工作"(即使n不是G中的节点时可能出错),在 v2.x 中则会直接报错:
>>> # dangerous in v1.x, not allowed in v2.x >>> G.node[n] = H.node[n] # 会报错请改用更安全的版本:
>>> G.nodes[n].update(H.nodes[n]) # works in v2.x三、EdgeView:G.edges取代G.edge与edges_iter
G.edges现在返回EdgeView而非边列表,同样支持集合运算:
>>> G.edges # for backward compatibility G.edges() works as well EdgeView([(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]) >>> list(G.edges) [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]边数据通过G.edges[u, v]查询(取代G.edge[u][v])。EdgeView的可调用形态G.edges(data='weight', default=1)会生成EdgeDataView:根据data与default参数,分别迭代二元组(u, v)(data=False,默认)、三元组(u, v, datadict)(data=True)或(u, v, datadict.get(data, default));nbunch参数可将迭代范围限制为与某组节点相邻的边。这些细节均定义在 networkx/classes/reportviews.py。
EdgeDataView只支持迭代与成员测试,不支持集合运算;对无向图而言,(0, 1)与(1, 0)并不是唯一表示,因此做集合运算时需保证参与比较的集合使用唯一的边表示。
对G.edge属性的替代:迁移指南给出的通用建议是——把所有G.edge替换为G.adj。G.adj在 v1 中就是G.edge,在 v2 中继续可用,因此在两代版本间都能工作。在 networkx/classes/graph.py 中,adj是返回AdjacencyView的cached_property,文档明确说明其结构为"以节点为键、邻居字典为值的只读 dict 式结构",G.adj[3][2]['color'] = 'blue'可以设置边(3, 2)的颜色属性。
四、DegreeView:G.degree与in_degree/out_degree
G.degree返回DegreeView。与其他视图相比,它的"字典性"更弱:按(node, degree)二元组迭代,不提供keys/values/items/get方法;但支持G.degree[n]的单项查询。需要以节点为键的度数字典时,dict(G.degree)即可生成:
>>> G.degree # for backward compatibility G.degree() works as well DegreeView({0: 4, 1: 4, 2: 4, 3: 4, 4: 4}) >>> G.degree([1, 2, 3]) DegreeView({1: 4, 2: 4, 3: 4}) >>> list(G.degree([1, 2, 3])) [(1, 4), (2, 4), (3, 4)] >>> dict(G.degree([1, 2, 3])) {1: 4, 2: 4, 3: 4} >>> list(G.degree) [(0, 4), (1, 4), (2, 4), (3, 4), (4, 4)] >>> dict(G.degree) {0: 4, 1: 4, 2: 4, 3: 4, 4: 4}nbunch参数可以将视图限制为只包含指定节点(如上面的G.degree([1, 2, 3]))。单个节点的度数用G.degree[node]计算。
有向图的in_degree/out_degree也做了同样改造。若只想取度数数值,有几种风格可选(以DiGraph的in_degree为例,out_degree与degree同理):
>>> DG = nx.DiGraph() >>> DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)]) >>> deg = DG.in_degree # sets up the view >>> [d for n, d in deg] # gets all nodes' degree values [1, 1, 0] >>> (d for n, d in deg) # iterator over degree values <generator object <genexpr> ...> >>> [deg[n] for n in [1, 3]] # using lookup for only some nodes [1, 0]以下写法在 v1 与 v2 中都能工作:
>>> for node, in_deg in dict(DG.in_degree).items(): # works for nx1 and nx2 ... print(node, in_deg) 1 1 2 1 3 0 >>> dict(DG.in_degree([1, 3])).values() # works for nx1 and nx2 dict_values([1, 0]) >>> list(d for n, d in DG.in_degree([1, 3])) # DG.in_degree(nlist) 创建仅含 nlist 中节点的受限视图 [1, 0]若追求极致性能,还可以直接借助底层邻接结构(pred/adj),这种方式同样不受 API 变更影响:
>>> [len(nbrs) for n, nbrs in DG.pred.items()] # probably slightly fastest for all nodes [1, 1, 0] >>> [len(DG.pred[n]) for n in [1, 3]] # probably slightly faster for only some nodes [1, 0]DegreeView的完整语义见 networkx/classes/reportviews.py:有向图中G.degree同时统计入边与出边,G.in_degree/G.out_degree只统计单一方向;还支持加权度G.degree(weight='attr_name');度数视图不提供集合运算,需要集合特性时请用NodeView。
五、邻居迭代:G.neighbors与有向图视图
如果n是G中的节点,G.neighbors(n)返回迭代器(不再是列表):
>>> n = 1 >>> G.neighbors(n) <dict_keyiterator object at ...> >>> list(G.neighbors(n)) [0, 2, 3, 4]DiGraph的视图与Graph类似,但拥有更多方法(in_edges/out_edges/successors/predecessors/in_degree/out_degree):
>>> D = nx.DiGraph() >>> D.add_edges_from([(1, 2), (2, 3), (1, 3), (2, 4)]) >>> D.nodes NodeView((1, 2, 3, 4)) >>> list(D.nodes) [1, 2, 3, 4] >>> D.edges OutEdgeView([(1, 2), (1, 3), (2, 3), (2, 4)]) >>> list(D.edges) [(1, 2), (1, 3), (2, 3), (2, 4)] >>> D.in_degree[2] 1 >>> D.out_degree[2] 2 >>> D.in_edges InEdgeView([(1, 2), (2, 3), (1, 3), (2, 4)]) >>> list(D.in_edges()) [(1, 2), (2, 3), (1, 3), (2, 4)] >>> D.out_edges(2) OutEdgeDataView([(2, 3), (2, 4)]) >>> list(D.out_edges(2)) [(2, 3), (2, 4)] >>> D.in_degree InDegreeView({1: 0, 2: 1, 3: 2, 4: 1}) >>> list(D.in_degree) [(1, 0), (2, 1), (3, 2), (4, 1)] >>> D.successors(2) <dict_keyiterator object at ...> >>> list(D.successors(2)) [3, 4] >>> D.predecessors(2) <dict_keyiterator object at ...> >>> list(D.predecessors(2)) [1]注意D.edges默认是OutEdgeView(出边视图),D.in_edges提供入边视图;out_edges(2)只返回节点 2 的出边。同样的变更适用于MultiGraph与MultiDiGraph,唯一的差别是多重图边默认迭代为(u, v, key)三元组,可用G.edges(keys=False)取得二元组(见 networkx/classes/reportviews.py)。
六、set_node_attributes/set_edge_attributes:参数顺序对调
set_node_attributes与set_edge_attributes的参数顺序发生了变化:name与values的位置互换,且name现在默认为None。旧签名(graph, name, value)改为(graph, value, name=None)。新签名允许省略name,直接把"节点到属性字典的字典"传给values。
在源码 networkx/classes/function.py 中,set_node_attributes(G, values, name=None)的文档字符串直接给出了警告:
Warning: The call order of arguments
valuesandnameswitched between v1.x & v2.x.
values的行为规则如下:
- 若非字典,则视为单个属性值,应用到
G的所有节点,属性名取name(例如nx.set_node_attributes(G, labels, "labels")); - 若是字典,则以节点为键映射到属性值或属性字典;若传入"字典的字典",外层字典按节点映射到内层属性字典,此时可省略
name。
旧代码若不改会立刻失败:
>>> G = nx.Graph([(1, 2), (1, 3)]) >>> nx.set_node_attributes(G, 'label', {1: 'one', 2: 'two', 3: 'three'}) # 新版本报错 >>> nx.set_edge_attributes(G, 'label', {(1, 2): 'path1', (2, 3): 'path2'}) # 新版本报错在 v2.0 中会触发TypeError: unhashable type: 'dict'。重构为显式关键字参数即可:
>>> G = nx.Graph([(1, 2), (1, 3)]) >>> nx.set_node_attributes(G, name='label', values={1: 'one', 2: 'two', 3: 'three'}) >>> nx.set_edge_attributes(G, name='label', values={(1, 2): 'path1', (2, 3): 'path2'})这是迁移中最简单的技巧:显式写出关键字参数名。它向后兼容、保证参数正确传递,无论参数顺序如何,因而也是后面"双版本兼容代码"章节的核心手段。get_node_attributes/get_edge_attributes同样受此影响。
七、方法迁移:从图类到主命名空间的函数
以下方法从基础图类移动到了 networkx 主命名空间:
| v1.x 图方法 | v2.x 命名空间函数 |
|---|---|
G.add_path | nx.add_path(G, ...) |
G.add_star | nx.add_star(G, ...) |
G.add_cycle | nx.add_cycle(G, ...) |
G.number_of_selfloops | nx.number_of_selfloops(G) |
G.nodes_with_selfloops | nx.nodes_with_selfloops(G) |
G.selfloop_edges | nx.selfloop_edges(G) |
这些函数的当前实现位于 networkx/classes/function.py:
add_star/add_path/add_cycle分别在 function.py、function.py、function.py;nodes_with_selfloops在 function.py,selfloop_edges(G, data=False, keys=False, default=None)在 function.py,number_of_selfloops在 function.py。
为了向后兼容,v2.x 中保留了这些方法的废弃版本(deprecated methods)——调用G.add_path(...)仍会工作,但会收到弃用警告。注意这些函数采用nx.add_path(G, nodes, ...)的显式图参数风格,与旧的G.add_path(nodes, ...)调用方式不同。
八、GraphViews 与数据复制语义:as_view、fresh_copy、root_graph
G.subgraph/edge_subgraph/reverse/to_directed/to_undirected创建新图的方式发生了改变:多数支持as_view=True选项以创建视图而非复制数据,且默认数据复制深度也可能不同。
在 networkx/classes/graph.py 中可以看到:
to_directed(self, as_view=False)(graph.py);to_undirected(self, as_view=False)(graph.py);subgraph(self, nodes)(graph.py)。
DiGraph/MultiGraph/MultiDiGraph也有对应实现,如 networkx/classes/digraph.py、networkx/classes/multigraph.py、networkx/classes/multigraph.py。
视图带来两个重要推论:
- 不要假设
G.__class__()能创建与G相同类型的新图实例。当G是 SubGraph、ReversedGraph 等视图时,__class__的调用签名与基类不同。v2.x 中应使用G.fresh_copy()创建正确类型的空图,再填充节点与边。 - 视图可以嵌套:视图的视图的视图……若要追溯到链条最底层的原始图,使用
G.root_graph。但需注意,root_graph返回的图可能与视图的有向/无向类型不同(例如无向图的reverse视图其根图仍然是无向的),使用时务必确认类型。
九、topological_sort的参数收窄
topological_sort不再接受reverse与nbunch参数。当前实现位于 networkx/algorithms/dag.py,是一个纯生成器函数,且对无向图抛出NetworkXError、对含环图抛出NetworkXUnfeasible。
如果nbunch原本是单个源节点,现在可以用subgraph运算符实现同样效果:
nx.topological_sort(G.subgraph(nx.descendants(G, nbunch)))要实现逆拓扑序,先把输出转为列表再反转:
reversed(list(nx.topological_sort(G)))十、编写同时兼容 v1.x 与 v2.x 的代码
迁移指南专门用一节讨论"双版本兼容"(Writing code that works for both versions)。核心原则如下:
10.1 用关键字参数调用属性设置函数
set_node_attributes/get_node_attributes/set_edge_attributes/get_edge_attributes的name与values顺序已变,两代版本都兼容的写法是显式使用关键字:
>>> nx.set_node_attributes(G, values=1.0, name='weight')10.2 去掉方法名中的_iter
把任何带_iter的方法改为不带_iter的版本:
- v1 中:原版本用列表代替迭代器,代码依然能跑;
- v2 中:新版本创建视图(行为类似迭代器)。
10.3 用G.adj替换G.edge
G.edge属性已删除,而G.adj在 v1 中就是G.edge,两代通用。
10.4 节点属性访问的双版本写法
G.node.items()(v1.x)→G.nodes(data=True)(v2.x 与 v1.x 均可用);- 遍历
for n in G.node:→ 直接for n in G:; - 绝大多数
G.node用法都能找到双版本通用写法,唯一例外是G.node[n]:v2.x 中是G.nodes[n],这在 v1.x 中不可用。
好在 v2.x 中仍保留了G.node作为过渡指针,因此需要兼容 v1.x 时仍可继续写G.node[n](官方计划在 v3.x 移除G.node)。也就是说:面向纯 v2.x 的代码用G.nodes[n],面向双版本的代码用G.node[n]。
10.5 复制节点属性字典
G.node[n] = H.node[n]在 v1.x 中有隐患、在 v2.x 中直接报错,双版本安全写法:
>>> G.nodes[n].update(H.nodes[n]) # works in v2.x10.6 被迁移方法的双版本适配
被移到主命名空间的方法可通过废弃方法继续工作。若想彻底改用新函数,一个双版本 hack 是:按 v2.x 写法编码,再为 v1.x 命名空间临时补上同名函数:
>>> if nx.__version__[0] == '1': ... nx.add_path = lambda G, nodes: G.add_path(nodes)而G.fresh_copy()/G.root_graph这类纯 v2.x 机制很难在 v1.x 上兼容,此时最好显式确定目标图类型,直接调用Graph/DiGraph/MultiGraph/MultiDiGraph构造函数。
十一、Pickle 跨版本:v1 与 v2 的序列化兼容
Pickle 协议只存储数据、不存储类方法。因此v1 写入的 pickle 文件不能指望被 v2 直接读回图对象。
如果确实遇到了这种情况,官方建议的迁移路径是:在装有 v1 的环境中读入 pickle,把节点与边信息写出来;再到装有 v2 的环境中读取这些节点/边数据,添加进一个全新图:
>>> # in v1.x >>> pickle.dump([G.nodes(data=True), G.edges(data=True)], file) >>> # then in v2.x >>> nodes, edges = pickle.load(file) >>> G = nx.Graph() >>> G.add_nodes_from(nodes) >>> G.add_edges_from(edges)十二、其他值得注意的改进
除基础图类外,2.0 还对代码库整体做了大量改进,文档明确提到的两处:
drawing/nx_pylab中节点居中(centering)的改进:绘图时节点定位更规整,相关代码位于 networkx/drawing/nx_pylab.py;- 部分
shortest_path例程由字典输出改为迭代器输出:涉及 networkx/algorithms/shortest_paths 目录下的相关函数,调用方需要注意返回类型的差异。
结语
从 1.X 到 2.0 的迁移,本质上是一次"把图数据访问方式从列表/字典复制,升级为轻量、实时、支持集合运算的视图体系"的 API 现代化。虽然破坏性改动较多,但绝大多数失败都以清晰的异常形式呈现,配合本文梳理的替换映射表(G.node→G.nodes、G.edge→G.adj/G.edges、*_iter方法删除、属性设置函数参数重排、图方法迁移为命名空间函数、fresh_copy/root_graph替代__class__()),即可在最短时间内完成存量代码的升级。若仍需在过渡期维护老代码,请优先采用本文第十章的关键字参数与双版本惯用法。
【免费下载链接】networkxNetwork Analysis in Python项目地址: https://gitcode.com/gh_mirrors/ne/networkx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考