Statistics
| Branch: | Revision:

iof-tools / networkxMiCe / networkx-master / doc / release / migration_guide_from_1.x_to_2.0.rst @ 5cef0f13

History | View | Annotate | Download (13.1 KB)

1
:orphan:
2

    
3
*******************************
4
Migration guide from 1.X to 2.0
5
*******************************
6

    
7
This is a guide for people moving from NetworkX 1.X to NetworkX 2.0
8

    
9
Any issues with these can be discussed on the `mailing list
10
<https://groups.google.com/forum/#!forum/networkx-discuss>`_.
11

    
12
At the bottom of this document we discuss how to create code that will
13
work with both NetworkX v1.x and v2.0.
14

    
15
We have made some major changes to the methods in the Multi/Di/Graph classes.
16
The methods changed are explained with examples below.
17

    
18
With the release of NetworkX 2.0 we are moving to a view/iterator reporting API.
19
We have changed many methods from reporting lists or dicts to iterating over
20
the information. Most of the changes in this regard are in the base classes.
21
Methods that used to return containers now return views (inspired from
22
`dictionary views <https://docs.python.org/3/library/stdtypes.html#dict-views>`_
23
in Python) and methods that returned an iterator have been removed.
24
The methods which create new graphs have changed in the depth of data copying.
25
``G.subgraph``/``edge_subgraph``/``reverse``/``to_directed``/``to_undirected``
26
are affected.  Many now have options for view creation instead of copying data.
27
The depth of the data copying may have also changed.
28

    
29
One View example is ``G.nodes`` (or ``G.nodes()``) which now returns a
30
dict-like NodeView while ``G.nodes_iter()`` has been removed. Similarly
31
for views with ``G.edges`` and removing ``G.edges_iter``.
32
The Graph attributes ``G.node`` and ``G.edge`` have been removed in favor of
33
using ``G.nodes[n]`` and ``G.edges[u, v]``.
34
Finally, the ``selfloop`` methods and ``add_path``/``star``/``cycle`` have
35
been moved from graph methods to networkx functions.
36

    
37
We expect that these changes will break some code. We have tried to make
38
them break the code in ways that raise exceptions, so it will be obvious
39
that code is broken.
40

    
41
There are also a number of improvements to the codebase outside of the base
42
graph classes. These are too numerous to catalog here, but a couple obvious
43
ones include:
44

    
45
- centering of nodes in ``drawing/nx_pylab``,
46
- iterator vs dict output from a few ``shortest_path`` routines
47

    
48
-------
49

    
50
Some demonstrations:
51

    
52
    >>> import networkx as nx
53
    >>> G = nx.complete_graph(5)
54
    >>> G.nodes  # for backward compatibility G.nodes() works as well
55
    NodeView((0, 1, 2, 3, 4))
56

    
57
You can iterate through ``G.nodes`` (or ``G.nodes()``)
58

    
59
    >>> for node in G.nodes:
60
    ...     print(node)
61
    0
62
    1
63
    2
64
    3
65
    4
66

    
67
If you want a list of nodes you can use the Python list function
68

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

    
72
``G.nodes`` is set-like allowing set operations. It is also dict-like in that you
73
can look up node data with ``G.nodes[n]['weight']``. You can still use the calling
74
interface ``G.nodes(data='weight')`` to iterate over node/data pairs. In addition
75
to the dict-like views ``keys``/``values``/``items``, ``G.nodes`` has a data-view
76
G.nodes.data('weight').  The new EdgeView ``G.edges`` has similar features for edges.
77

    
78
By adding views NetworkX supports some new features like set operations on
79
views.
80

    
81
    >>> H = nx.Graph()
82
    >>> H.add_nodes_from([1, 'networkx', '2.0'])
83
    >>> G.nodes & H.nodes  # finding common nodes in 2 graphs
84
    {1}
85
    >>> # union of nodes in 2 graphs
86
    >>> G.nodes | H.nodes  # doctest: +SKIP
87
    {0, 1, 2, 3, 4, 'networkx', '2.0'}
88

    
89
Similarly, ``G.edges`` now returns an EdgeView instead of a list of edges and it
90
also supports set operations.
91

    
92
    >>> G.edges  # for backward compatibility G.nodes() works as well
93
    EdgeView([(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)])
94
    >>> list(G.edges)
95
    [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
96

    
97
``G.degree`` now returns a DegreeView. This is less dict-like than the other views
98
in the sense that it iterates over (node, degree) pairs, does not provide
99
keys/values/items/get methods. It does provide lookup ``G.degree[n]`` and
100
``(node, degree)`` iteration. A dict keyed by nodes to degree values can be
101
easily created if needed as ``dict(G.degree)``.
102

    
103
    >>> G.degree  # for backward compatibility G.degree() works as well
104
    DegreeView({0: 4, 1: 4, 2: 4, 3: 4, 4: 4})
105
    >>> G.degree([1, 2, 3])
106
    DegreeView({1: 4, 2: 4, 3: 4})
107
    >>> list(G.degree([1, 2, 3]))
108
    [(1, 4), (2, 4), (3, 4)]
109
    >>> dict(G.degree([1, 2, 3]))
110
    {1: 4, 2: 4, 3: 4}
111
    >>> G.degree
112
    DegreeView({0: 4, 1: 4, 2: 4, 3: 4, 4: 4})
113
    >>> list(G.degree)
114
    [(0, 4), (1, 4), (2, 4), (3, 4), (4, 4)]
115
    >>> dict(G.degree)
116
    {0: 4, 1: 4, 2: 4, 3: 4, 4: 4}
117

    
118
The degree of an individual node can be calculated by ``G.degree[node]``.
119
Similar changes have been made to ``in_degree`` and ``out_degree``
120
for directed graphs. If you want just the degree values, here are some options.
121
They are shown for ``in_degree`` of a ``DiGraph``, but similar ideas work
122
for ``out_degree`` and ``degree``
123

    
124
    >>> DG = nx.DiGraph()
125
    >>> DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)])
126
    >>> deg = DG.in_degree   # sets up the view
127
    >>> [d for n, d in deg]   # gets all nodes' degree values
128
    [1, 1, 0]
129
    >>> (d for n, d in deg)    # iterator over degree values
130
    <generator object <genexpr> ...>
131
    >>> [deg[n] for n in [1, 3]]   # using lookup for only some nodes
132
    [1, 0]
133

    
134
    >>> dict(DG.in_degree([1, 3])).values()    # works for nx-1 and nx-2
135
    dict_values([1, 0])
136
    >>> # DG.in_degree(nlist) creates a restricted view for only nodes in nlist.
137
    >>> # but see the fourth option above for using lookup instead.
138
    >>> list(d for n, d in DG.in_degree([1, 3]))
139
    [1, 0]
140

    
141
    >>> [len(nbrs) for n, nbrs in DG.pred.items()]  # probably slightly fastest for all nodes
142
    [1, 1, 0]
143
    >>> [len(DG.pred[n]) for n in [1, 3]]           # probably slightly faster for only some nodes
144
    [1, 0]
145

    
146
-------
147

    
148
If ``n`` is a node in ``G``, then ``G.neighbors(n)`` returns an iterator.
149

    
150
    >>> n = 1
151
    >>> G.neighbors(n)
152
    <dict_keyiterator object at ...>
153
    >>> list(G.neighbors(n))
154
    [0, 2, 3, 4]
155

    
156
DiGraphViews behave similar to GraphViews, but have a few more methods.
157

    
158
    >>> D = nx.DiGraph()
159
    >>> D.add_edges_from([(1, 2), (2, 3), (1, 3), (2, 4)])
160
    >>> D.nodes
161
    NodeView((1, 2, 3, 4))
162
    >>> list(D.nodes)
163
    [1, 2, 3, 4]
164
    >>> D.edges
165
    OutEdgeView([(1, 2), (1, 3), (2, 3), (2, 4)])
166
    >>> list(D.edges)
167
    [(1, 2), (1, 3), (2, 3), (2, 4)]
168
    >>> D.in_degree[2]
169
    1
170
    >>> D.out_degree[2]
171
    2
172
    >>> D.in_edges
173
    InEdgeView([(1, 2), (2, 3), (1, 3), (2, 4)])
174
    >>> list(D.in_edges())
175
    [(1, 2), (2, 3), (1, 3), (2, 4)]
176
    >>> D.out_edges(2)
177
    OutEdgeDataView([(2, 3), (2, 4)])
178
    >>> list(D.out_edges(2))
179
    [(2, 3), (2, 4)]
180
    >>> D.in_degree
181
    InDegreeView({1: 0, 2: 1, 3: 2, 4: 1})
182
    >>> list(D.in_degree)
183
    [(1, 0), (2, 1), (3, 2), (4, 1)]
184
    >>> D.successors(2)
185
    <dict_keyiterator object at ...>
186
    >>> list(D.successors(2))
187
    [3, 4]
188
    >>> D.predecessors(2)
189
    <dict_keyiterator object at ...>
190
    >>> list(D.predecessors(2))
191
    [1]
192

    
193
The same changes apply to MultiGraphs and MultiDiGraphs.
194

    
195
-------
196

    
197
The order of arguments to ``set_edge_attributes`` and ``set_node_attributes`` has
198
changed.  The position of ``name`` and ``values`` has been swapped, and ``name`` now
199
defaults to ``None``.  The previous call signature of ``(graph, name, value)`` has
200
been changed to ``(graph, value, name=None)``. The new style allows for ``name`` to
201
be omitted in favor of passing a dictionary of dictionaries to ``values``.
202

    
203
A simple method for migrating existing code to the new version is to explicitly
204
specify the keyword argument names. This method is backwards compatible and
205
ensures the correct arguments are passed, regardless of the order. For example the old code
206

    
207
    >>> G = nx.Graph([(1, 2), (1, 3)])
208
    >>> nx.set_node_attributes(G, 'label', {1: 'one', 2: 'two', 3: 'three'})  # doctest: +SKIP
209
    >>> nx.set_edge_attributes(G, 'label', {(1, 2): 'path1', (2, 3): 'path2'})  # doctest: +SKIP
210

    
211
Will cause ``TypeError: unhashable type: 'dict'`` in the new version. The code
212
can be refactored as
213

    
214
    >>> G = nx.Graph([(1, 2), (1, 3)])
215
    >>> nx.set_node_attributes(G, name='label', values={1: 'one', 2: 'two', 3: 'three'})
216
    >>> nx.set_edge_attributes(G, name='label', values={(1, 2): 'path1', (2, 3): 'path2'})
217

    
218
-------
219

    
220
Some methods have been moved from the base graph class into the main namespace.
221
These are:  ``G.add_path``, ``G.add_star``, ``G.add_cycle``, ``G.number_of_selfloops``,
222
``G.nodes_with_selfloops``, and ``G.selfloop_edges``.
223
They are replaced by ``nx.path_graph(G, ...)`` ``nx.add_star(G, ...)``,
224
``nx.selfloop_edges(G)``, etc.
225
For backward compatibility, we are leaving them as deprecated methods.
226

    
227
-------
228

    
229
With the new GraphViews (SubGraph, ReversedGraph, etc) you can't assume that
230
``G.__class__()`` will create a new instance of the same graph type as ``G``.
231
In fact, the call signature for ``__class__`` differs depending on whether ``G``
232
is a view or a base class. For v2.x you should use ``G.fresh_copy()`` to
233
create a null graph of the correct type---ready to fill with nodes and edges.
234

    
235
Graph views can also be views-of-views-of-views-of-graphs. If you want to find the
236
original graph at the end of this chain use ``G.root_graph``. Be careful though
237
because it may be a different graph type (directed/undirected) than the view.
238

    
239
-------
240

    
241
``topolgical_sort``  no longer accepts ``reverse`` or ``nbunch`` arguments.
242
If ``nbunch`` was a single node source, then the same effect can now be achieved
243
using the ``subgraph`` operator:
244

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

    
247
To achieve a reverse topological sort, the output should be converted to a list:
248

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

    
251
-------
252

    
253
Writing code that works for both versions
254
=========================================
255

    
256
Methods ``set_node_attributes``/``get_node_attributes``/``set_edge_attributes``/``get_edge_attributes``
257
have changed the order of their keyword arguments ``name`` and ``values``. So, to make it
258
work with both versions you should use the keywords in your call.
259

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

    
262
-------
263

    
264
Change any method with ``_iter`` in its name to the version without ``_iter``.
265
In v1 this replaces an iterator by a list, but the code will still work.
266
In v2 this creates a view (which acts like an iterator).
267

    
268
-------
269

    
270
Replace any use of ``G.edge`` with ``G.adj``. The Graph attribute ``edge``
271
has been removed. The attribute ``G.adj`` is ``G.edge`` in v1 and will work
272
with both versions.
273

    
274
-------
275

    
276
If you use ``G.node.items()`` or similar in v1.x, you can replace it with
277
``G.nodes(data=True)`` which works for v2.x and v1.x.  Iterating over ``G.node```
278
as in ``for n in G.node:`` can be replaced with ``G``, as in: ``for n in G:``.
279

    
280
-------
281

    
282
The Graph attribute ``node`` has moved its functionality to ``G.nodes``, so code
283
expected to work with v2.x should use ``G.nodes``.
284
In fact most uses of ``G.node`` can be replaced by an idiom that works for both
285
versions. The functionality that can't easily is: ``G.node[n]``.
286
In v2.x that becomes ``G.nodes[n]`` which doesn't work in v1.x.
287

    
288
Luckily you can still use ``G.node[n]`` in v2.x when you want it to be able to work
289
with v1.x too. We have left ``G.node`` in v2.x as a transition pointer to ``G.nodes``.
290
We envision removing ``G.node`` in v3.x (sometime in the future).
291

    
292
-------
293

    
294
Copying node attribute dicts directly from one graph to another can corrupt
295
the node data structure if not done correctly. Code such as the following:
296

    
297
    >>> # dangerous in v1.x, not allowed in v2.x
298
    >>> G.node[n] = H.node[n]  # doctest: +SKIP
299

    
300
used to work, even though it could cause errors if ``n`` was not a node in ``G``.
301
That code will cause an error in v2.x.  Replace it with one of the more safe versions:
302

    
303
    >>> G.node[n].update(H.node[n])  # works in both v1.x and v2.x
304
    >>> G.nodes[n].update(H.nodes[n])  # works in v2.x
305

    
306
-------
307

    
308
The methods removed from the graph classes and put into the main package namespace
309
can be used via the associated deprecated methods. If you want to update your code
310
to the new functions, one hack to make that work with both versions is to write
311
your code for v2.x and add code to the v1 namespace in an ad hoc manner:
312

    
313
    >>> if nx.__version__[0] == '1':
314
    ...     nx.add_path = lambda G, nodes: G.add_path(nodes)
315

    
316
Similarly, v2.x code that uses ``G.fresh_copy()`` or ``G.root_graph`` is hard to make
317
work for v1.x. It may be best in this case to determine the graph type you want
318
explicitly and call Graph/DiGraph/MultiGraph/MultiDiGraph directly.
319

    
320
Using Pickle with v1 and v2
321
===========================
322

    
323
The Pickle protocol does not store class methods, only the data. So if you write a
324
pickle file with v1 you should not expect to read it into a v2 Graph. If this happens
325
to you, read it in with v1 installed and write a file with the node and edge
326
information. You can read that into a config with v2 installed and then add those nodes
327
and edges to a fresh graph. Try something similar to this:
328

    
329
    >>> # in v1.x
330
    >>> pickle.dump([G.nodes(data=True), G.edges(data=True)], file)  # doctest: +SKIP
331
    >>> # then in v2.x
332
    >>> nodes, edges = pickle.load(file)  # doctest: +SKIP
333
    >>> G = nx.Graph()  # doctest: +SKIP
334
    >>> G.add_nodes_from(nodes)  # doctest: +SKIP
335
    >>> G.add_edges_from(edges)  # doctest: +SKIP