从1.X迁移到2.0的迁移指南#

这是一个供从NetworkX 1.X迁移到NetworkX 2.0的人参考的指南。

如有任何问题,可以在 邮件列表 上讨论。

在本文档底部,我们将讨论如何创建能同时与NetworkX v1.x和v2.0兼容的代码。

我们对Multi/Di/Graph类中的方法进行了一些重大更改。下面通过示例解释了这些更改的方法。

随着NetworkX 2.0的发布,我们正在转向视图/迭代器报告API。 我们已经将许多方法从报告列表或字典更改为遍历信息。在这方面的大部分更改都在基类中。 以前返回容器的方法现在返回视图(灵感来自Python中的 字典视图 ), 并且已删除返回迭代器的方法。 创建新图的方法在数据复制深度上发生了变化。

G.subgraph / edge_subgraph / reverse / to_directed / to_undirected

受到影响。现在,许多方法提供了创建视图而不是复制数据的选项。 数据复制的深度也可能已经发生了变化。

一个视图示例是 G.nodes (或 G.nodes() ),它现在返回类似字典的NodeView,而 G.nodes_iter() 已被移除。类似地, 对于 G.edges 视图,移除了 G.edges_iter 。 图属性 G.nodeG.edge 已被移除,推荐使用 G.nodes[n]G.edges[u, v] 。 最后, selfloop 方法和 add_path / star / cycle 已从图方法移动到networkx函数中。

我们预计这些更改会破坏一些代码。我们尽力使它们以引发异常的方式破坏代码,这样就很明显代码已经损坏了。

除了基本图类之外,代码库中还有许多改进。这些改进太多,无法在此列出,但其中一些明显的包括:

  • drawing/nx_pylab 中对节点进行居中处理,

  • 从一些 shortest_path 例程中输出迭代器与字典的区别


一些演示:

>>> import networkx as nx
>>> G = nx.complete_graph(5)
>>> G.nodes  # 为了向后兼容,G.nodes() 也可以使用
NodeView((0, 1, 2, 3, 4))

您可以遍历 G.nodes (或 G.nodes()

>>> for node in G.nodes:
...     print(node)
0
1
2
3
4

如果您想要节点列表,可以使用Python的列表函数

>>> list(G.nodes)
[0, 1, 2, 3, 4]

G.nodes 类似于集合,允许进行集合操作。它也类似于字典,因为您可以使用 G.nodes[n]['weight'] 查找节点数据。

您仍然可以使用调用接口 G.nodes(data='weight') 遍历节点/数据对。除了类似字典的视图 keys / values / itemsG.nodes 还有一个数据视图。 G.nodes.data(‘weight’). 新的EdgeView G.edges 对边具有类似的特性。

通过添加视图,NetworkX支持一些新功能,如在视图上进行集合操作。

>>> H = nx.Graph()
>>> H.add_nodes_from([1, 'networkx', '2.0'])
>>> G.nodes & H.nodes  # 查找两个图中的公共节点
{1}
>>> # 两个图中节点的并集
>>> G.nodes | H.nodes  
{0, 1, 2, 3, 4, 'networkx', '2.0'}

类似地, G.edges 现在返回一个EdgeView而不是边的列表,它也支持集合操作。

>>> G.edges  # 为了向后兼容,G.nodes() 也可以使用
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.degree 现在返回一个DegreeView。与其他视图不同,它不太像字典,因为它遍历(node, degree)对,不提供keys/values/items/get方法。它提供查找 G.degree[n](node, degree) 迭代。如果需要,可以轻松地创建一个由节点键控的字典到度值,如 dict(G.degree)

>>> G.degree  # 为了向后兼容,G.degree() 也可以使用
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}
>>> G.degree
DegreeView({0: 4, 1: 4, 2: 4, 3: 4, 4: 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}

可以通过 G.degree[node] 计算单个节点的度。对于有向图, in_degreeout_degree 也进行了类似的更改。如果只想要度的值,这里有一些选项。这些选项是针对 DiGraphin_degree 显示的,但类似的想法也适用于 out_degreedegree

>>> DG = nx.DiGraph()
>>> DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)])
>>> deg = DG.in_degree   # 设置视图
>>> [d for n, d in deg]   # 获取所有节点的度值
[1, 1, 0]
>>> (d for n, d in deg)    # 度值的迭代器
<generator object <genexpr> ...>
>>> [deg[n] for n in [1, 3]]   # 仅使用查找来获取部分节点的度值
[1, 0]
>>> for node, in_deg in dict(DG.in_degree).items():  # 对nx1和nx2都适用
...     print(node, in_deg)
1 1
2 1
3 0
>>> dict(DG.in_degree([1, 3])).values()    # 对nx1和nx2都适用
dict_values([1, 0])
>>> # DG.in_degree(nlist) 创建一个仅包含nlist中节点的受限视图。
>>> # 但是请参考上面的第四个选项,使用查找而不是视图。
>>> list(d for n, d in DG.in_degree([1, 3]))
[1, 0]
>>> [len(nbrs) for n, nbrs in DG.pred.items()]  # 对所有节点可能稍微更快
[1, 1, 0]
>>> [len(DG.pred[n]) for n in [1, 3]]           # 对于仅部分节点可能会稍微更快
[1, 0]

如果 nG 中的一个节点,则 G.neighbors(n) 返回一个迭代器。

>>> n = 1
>>> G.neighbors(n)
<dict_keyiterator object at ...>
>>> list(G.neighbors(n))
[0, 2, 3, 4]

DiGraphViews的行为类似于GraphViews,但有一些额外的方法。

>>> 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]

相同的更改也适用于MultiGraphs和MultiDiGraphs。


set_edge_attributesset_node_attributes 的参数顺序已更改。 namevalues 的位置已交换,并且 name 现在默认为 None 。之前的调用签名 (graph, name, value) 已更改为 (graph, value, name=None) 。新样式允许省略 name ,而是传递一个字典的字典给 values

将现有代码迁移到新版本的简单方法是显式指定关键字参数名称。这种方法向后兼容,确保传递正确的参数,无论顺序如何。例如,旧代码

>>> 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'})  

在新版本中会引发 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'})

一些方法已从基本图类移动到主命名空间。这些方法包括: G.add_pathG.add_starG.add_cycleG.number_of_selfloopsG.nodes_with_selfloopsG.selfloop_edges 。它们被 nx.path_graph(G, ...)nx.add_star(G, ...)nx.selfloop_edges(G) 等替代。为了向后兼容,我们将它们保留为弃用方法。#

使用新的GraphViews(SubGraph,ReversedGraph等),不能假定 G.__class__() 会创建与 G 相同图类型的新实例。 事实上, __class__ 的调用签名取决于 G 是视图还是基类。在v2.x中,您应该使用 G.fresh_copy() 来创建正确类型的空图—可以填充节点和边。

图视图也可以是图的视图的视图的视图。如果您想要找到此链条末端的原始图,请使用 G.root_graph 。但要小心,因为它可能与视图不同的图类型(有向/无向)。


topological_sort 不再接受 reversenbunch 参数。

如果 nbunch 是单个节点源,则现在可以使用 subgraph 运算符来实现相同的效果:

nx.topological_sort(G.subgraph(nx.descendants(G, nbunch)))

要实现反向拓扑排序,输出应转换为列表:

reversed(list(nx.topological_sort(G)))


编写适用于两个版本的代码#

方法 set_node_attributes / get_node_attributes / set_edge_attributes / get_edge_attributes 已更改其关键字参数 namevalues 的顺序。因此,为了使其适用于两个版本,您应在调用中使用关键字。

>>> nx.set_node_attributes(G, values=1.0, name='weight')

将任何带有 _iter 的方法更改为不带 _iter 的版本。在v1中,这将迭代器替换为列表,但代码仍将正常工作。 在v2中,这将创建一个视图(类似于迭代器)。


将任何使用 G.edge 替换为 G.adj 。已删除Graph属性 edge 。属性 G.adj 在v1中是 G.edge ,并且将适用于两个版本。


如果您在v1.x中使用 G.node.items() 或类似用法,则可以将其替换为 G.nodes(data=True) ,这适用于v2.x和v1.x。在 for n in G.node: 中迭代``G.node```可以替换为 G ,如: for n in G:


Graph属性 node 已将其功能移至 G.nodes ,因此预期与v2.x兼容的代码应使用 G.nodes 。 实际上,大多数使用 G.node 的地方都可以替换为适用于两个版本的习语。很难轻松替换的功能是: G.node[n] 。 在v2.x中,这变为 G.nodes[n] ,而在v1.x中不起作用。

幸运的是,在v2.x中,当您希望它能够与v1.x一起使用时,仍然可以使用 G.node[n] 。我们在v2.x中保留了 G.node 作为指向 G.nodes 的过渡指针。 我们设想在v3.x(将来的某个时候)中删除 G.node


直接从一个图复制节点属性字典到另一个图可能会损坏节点数据结构,如果不正确执行。以下代码可能会导致问题:

>>> # 在v1.x中有风险,在v2.x中不允许
>>> G.node[n] = H.node[n]  

即使可能会导致错误,也可以用于工作,如果 n 不是 G 中的一个节点。 这段代码在v2.x版本中会导致错误。用以下更安全的版本替换它:

>>> G.nodes[n].update(H.nodes[n])  # 在v2.x中有效

从图类中移除并放入主包命名空间的方法可以通过相关的弃用方法来使用。如果你想要将你的代码更新到新的函数,一个方法是通过一种临时的方式在v1命名空间中编写代码以使其同时适用于两个版本:

>>> 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。

在v1和v2中使用Pickle#

Pickle协议不存储类方法,只存储数据。因此,如果你用v1写入一个pickle文件,不应该期望将其读入v2图中。如果发生这种情况,可以在安装了v1的情况下读取它,并写入一个包含节点和边信息的文件。然后在安装了v2的情况下读取它,并将这些节点和边添加到一个新的图中。尝试类似于以下的操作:

>>> # 在v1.x中
>>> pickle.dump([G.nodes(data=True), G.edges(data=True)], file)  
>>> # 然后在v2.x中
>>> nodes, edges = pickle.load(file)  
>>> G = nx.Graph()  
>>> G.add_nodes_from(nodes)  
>>> G.add_edges_from(edges)