Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/classes/graph.py: 43%

287 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1"""Base class for undirected graphs. 

2 

3The Graph class allows any hashable object as a node 

4and can associate key/value attribute pairs with each undirected edge. 

5 

6Self-loops are allowed but multiple edges are not (see MultiGraph). 

7 

8For directed graphs see DiGraph and MultiDiGraph. 

9""" 

10from copy import deepcopy 

11from functools import cached_property 

12 

13import networkx as nx 

14from networkx import convert 

15from networkx.classes.coreviews import AdjacencyView 

16from networkx.classes.reportviews import DegreeView, EdgeView, NodeView 

17from networkx.exception import NetworkXError 

18 

19__all__ = ["Graph"] 

20 

21 

22class _CachedPropertyResetterAdj: 

23 """Data Descriptor class for _adj that resets ``adj`` cached_property when needed 

24 

25 This assumes that the ``cached_property`` ``G.adj`` should be reset whenever 

26 ``G._adj`` is set to a new value. 

27 

28 This object sits on a class and ensures that any instance of that 

29 class clears its cached property "adj" whenever the underlying 

30 instance attribute "_adj" is set to a new object. It only affects 

31 the set process of the obj._adj attribute. All get/del operations 

32 act as they normally would. 

33 

34 For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html 

35 """ 

36 

37 def __set__(self, obj, value): 

38 od = obj.__dict__ 

39 od["_adj"] = value 

40 if "adj" in od: 

41 del od["adj"] 

42 

43 

44class _CachedPropertyResetterNode: 

45 """Data Descriptor class for _node that resets ``nodes`` cached_property when needed 

46 

47 This assumes that the ``cached_property`` ``G.node`` should be reset whenever 

48 ``G._node`` is set to a new value. 

49 

50 This object sits on a class and ensures that any instance of that 

51 class clears its cached property "nodes" whenever the underlying 

52 instance attribute "_node" is set to a new object. It only affects 

53 the set process of the obj._adj attribute. All get/del operations 

54 act as they normally would. 

55 

56 For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html 

57 """ 

58 

59 def __set__(self, obj, value): 

60 od = obj.__dict__ 

61 od["_node"] = value 

62 if "nodes" in od: 

63 del od["nodes"] 

64 

65 

66class Graph: 

67 """ 

68 Base class for undirected graphs. 

69 

70 A Graph stores nodes and edges with optional data, or attributes. 

71 

72 Graphs hold undirected edges. Self loops are allowed but multiple 

73 (parallel) edges are not. 

74 

75 Nodes can be arbitrary (hashable) Python objects with optional 

76 key/value attributes, except that `None` is not allowed as a node. 

77 

78 Edges are represented as links between nodes with optional 

79 key/value attributes. 

80 

81 Parameters 

82 ---------- 

83 incoming_graph_data : input graph (optional, default: None) 

84 Data to initialize graph. If None (default) an empty 

85 graph is created. The data can be any format that is supported 

86 by the to_networkx_graph() function, currently including edge list, 

87 dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy 

88 sparse matrix, or PyGraphviz graph. 

89 

90 attr : keyword arguments, optional (default= no attributes) 

91 Attributes to add to graph as key=value pairs. 

92 

93 See Also 

94 -------- 

95 DiGraph 

96 MultiGraph 

97 MultiDiGraph 

98 

99 Examples 

100 -------- 

101 Create an empty graph structure (a "null graph") with no nodes and 

102 no edges. 

103 

104 >>> G = nx.Graph() 

105 

106 G can be grown in several ways. 

107 

108 **Nodes:** 

109 

110 Add one node at a time: 

111 

112 >>> G.add_node(1) 

113 

114 Add the nodes from any container (a list, dict, set or 

115 even the lines from a file or the nodes from another graph). 

116 

117 >>> G.add_nodes_from([2, 3]) 

118 >>> G.add_nodes_from(range(100, 110)) 

119 >>> H = nx.path_graph(10) 

120 >>> G.add_nodes_from(H) 

121 

122 In addition to strings and integers any hashable Python object 

123 (except None) can represent a node, e.g. a customized node object, 

124 or even another Graph. 

125 

126 >>> G.add_node(H) 

127 

128 **Edges:** 

129 

130 G can also be grown by adding edges. 

131 

132 Add one edge, 

133 

134 >>> G.add_edge(1, 2) 

135 

136 a list of edges, 

137 

138 >>> G.add_edges_from([(1, 2), (1, 3)]) 

139 

140 or a collection of edges, 

141 

142 >>> G.add_edges_from(H.edges) 

143 

144 If some edges connect nodes not yet in the graph, the nodes 

145 are added automatically. There are no errors when adding 

146 nodes or edges that already exist. 

147 

148 **Attributes:** 

149 

150 Each graph, node, and edge can hold key/value attribute pairs 

151 in an associated attribute dictionary (the keys must be hashable). 

152 By default these are empty, but can be added or changed using 

153 add_edge, add_node or direct manipulation of the attribute 

154 dictionaries named graph, node and edge respectively. 

155 

156 >>> G = nx.Graph(day="Friday") 

157 >>> G.graph 

158 {'day': 'Friday'} 

159 

160 Add node attributes using add_node(), add_nodes_from() or G.nodes 

161 

162 >>> G.add_node(1, time="5pm") 

163 >>> G.add_nodes_from([3], time="2pm") 

164 >>> G.nodes[1] 

165 {'time': '5pm'} 

166 >>> G.nodes[1]["room"] = 714 # node must exist already to use G.nodes 

167 >>> del G.nodes[1]["room"] # remove attribute 

168 >>> list(G.nodes(data=True)) 

169 [(1, {'time': '5pm'}), (3, {'time': '2pm'})] 

170 

171 Add edge attributes using add_edge(), add_edges_from(), subscript 

172 notation, or G.edges. 

173 

174 >>> G.add_edge(1, 2, weight=4.7) 

175 >>> G.add_edges_from([(3, 4), (4, 5)], color="red") 

176 >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})]) 

177 >>> G[1][2]["weight"] = 4.7 

178 >>> G.edges[1, 2]["weight"] = 4 

179 

180 Warning: we protect the graph data structure by making `G.edges` a 

181 read-only dict-like structure. However, you can assign to attributes 

182 in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change 

183 data attributes: `G.edges[1, 2]['weight'] = 4` 

184 (For multigraphs: `MG.edges[u, v, key][name] = value`). 

185 

186 **Shortcuts:** 

187 

188 Many common graph features allow python syntax to speed reporting. 

189 

190 >>> 1 in G # check if node in graph 

191 True 

192 >>> [n for n in G if n < 3] # iterate through nodes 

193 [1, 2] 

194 >>> len(G) # number of nodes in graph 

195 5 

196 

197 Often the best way to traverse all edges of a graph is via the neighbors. 

198 The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()` 

199 

200 >>> for n, nbrsdict in G.adjacency(): 

201 ... for nbr, eattr in nbrsdict.items(): 

202 ... if "weight" in eattr: 

203 ... # Do something useful with the edges 

204 ... pass 

205 

206 But the edges() method is often more convenient: 

207 

208 >>> for u, v, weight in G.edges.data("weight"): 

209 ... if weight is not None: 

210 ... # Do something useful with the edges 

211 ... pass 

212 

213 **Reporting:** 

214 

215 Simple graph information is obtained using object-attributes and methods. 

216 Reporting typically provides views instead of containers to reduce memory 

217 usage. The views update as the graph is updated similarly to dict-views. 

218 The objects `nodes`, `edges` and `adj` provide access to data attributes 

219 via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration 

220 (e.g. `nodes.items()`, `nodes.data('color')`, 

221 `nodes.data('color', default='blue')` and similarly for `edges`) 

222 Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`. 

223 

224 For details on these and other miscellaneous methods, see below. 

225 

226 **Subclasses (Advanced):** 

227 

228 The Graph class uses a dict-of-dict-of-dict data structure. 

229 The outer dict (node_dict) holds adjacency information keyed by node. 

230 The next dict (adjlist_dict) represents the adjacency information and holds 

231 edge data keyed by neighbor. The inner dict (edge_attr_dict) represents 

232 the edge data and holds edge attribute values keyed by attribute names. 

233 

234 Each of these three dicts can be replaced in a subclass by a user defined 

235 dict-like object. In general, the dict-like features should be 

236 maintained but extra features can be added. To replace one of the 

237 dicts create a new graph class by changing the class(!) variable 

238 holding the factory for that dict-like structure. 

239 

240 node_dict_factory : function, (default: dict) 

241 Factory function to be used to create the dict containing node 

242 attributes, keyed by node id. 

243 It should require no arguments and return a dict-like object 

244 

245 node_attr_dict_factory: function, (default: dict) 

246 Factory function to be used to create the node attribute 

247 dict which holds attribute values keyed by attribute name. 

248 It should require no arguments and return a dict-like object 

249 

250 adjlist_outer_dict_factory : function, (default: dict) 

251 Factory function to be used to create the outer-most dict 

252 in the data structure that holds adjacency info keyed by node. 

253 It should require no arguments and return a dict-like object. 

254 

255 adjlist_inner_dict_factory : function, (default: dict) 

256 Factory function to be used to create the adjacency list 

257 dict which holds edge data keyed by neighbor. 

258 It should require no arguments and return a dict-like object 

259 

260 edge_attr_dict_factory : function, (default: dict) 

261 Factory function to be used to create the edge attribute 

262 dict which holds attribute values keyed by attribute name. 

263 It should require no arguments and return a dict-like object. 

264 

265 graph_attr_dict_factory : function, (default: dict) 

266 Factory function to be used to create the graph attribute 

267 dict which holds attribute values keyed by attribute name. 

268 It should require no arguments and return a dict-like object. 

269 

270 Typically, if your extension doesn't impact the data structure all 

271 methods will inherit without issue except: `to_directed/to_undirected`. 

272 By default these methods create a DiGraph/Graph class and you probably 

273 want them to create your extension of a DiGraph/Graph. To facilitate 

274 this we define two class variables that you can set in your subclass. 

275 

276 to_directed_class : callable, (default: DiGraph or MultiDiGraph) 

277 Class to create a new graph structure in the `to_directed` method. 

278 If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used. 

279 

280 to_undirected_class : callable, (default: Graph or MultiGraph) 

281 Class to create a new graph structure in the `to_undirected` method. 

282 If `None`, a NetworkX class (Graph or MultiGraph) is used. 

283 

284 **Subclassing Example** 

285 

286 Create a low memory graph class that effectively disallows edge 

287 attributes by using a single attribute dict for all edges. 

288 This reduces the memory used, but you lose edge attributes. 

289 

290 >>> class ThinGraph(nx.Graph): 

291 ... all_edge_dict = {"weight": 1} 

292 ... 

293 ... def single_edge_dict(self): 

294 ... return self.all_edge_dict 

295 ... 

296 ... edge_attr_dict_factory = single_edge_dict 

297 >>> G = ThinGraph() 

298 >>> G.add_edge(2, 1) 

299 >>> G[2][1] 

300 {'weight': 1} 

301 >>> G.add_edge(2, 2) 

302 >>> G[2][1] is G[2][2] 

303 True 

304 """ 

305 

306 _adj = _CachedPropertyResetterAdj() 

307 _node = _CachedPropertyResetterNode() 

308 

309 node_dict_factory = dict 

310 node_attr_dict_factory = dict 

311 adjlist_outer_dict_factory = dict 

312 adjlist_inner_dict_factory = dict 

313 edge_attr_dict_factory = dict 

314 graph_attr_dict_factory = dict 

315 

316 def to_directed_class(self): 

317 """Returns the class to use for empty directed copies. 

318 

319 If you subclass the base classes, use this to designate 

320 what directed class to use for `to_directed()` copies. 

321 """ 

322 return nx.DiGraph 

323 

324 def to_undirected_class(self): 

325 """Returns the class to use for empty undirected copies. 

326 

327 If you subclass the base classes, use this to designate 

328 what directed class to use for `to_directed()` copies. 

329 """ 

330 return Graph 

331 

332 def __init__(self, incoming_graph_data=None, **attr): 

333 """Initialize a graph with edges, name, or graph attributes. 

334 

335 Parameters 

336 ---------- 

337 incoming_graph_data : input graph (optional, default: None) 

338 Data to initialize graph. If None (default) an empty 

339 graph is created. The data can be an edge list, or any 

340 NetworkX graph object. If the corresponding optional Python 

341 packages are installed the data can also be a 2D NumPy array, a 

342 SciPy sparse array, or a PyGraphviz graph. 

343 

344 attr : keyword arguments, optional (default= no attributes) 

345 Attributes to add to graph as key=value pairs. 

346 

347 See Also 

348 -------- 

349 convert 

350 

351 Examples 

352 -------- 

353 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

354 >>> G = nx.Graph(name="my graph") 

355 >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges 

356 >>> G = nx.Graph(e) 

357 

358 Arbitrary graph attribute pairs (key=value) may be assigned 

359 

360 >>> G = nx.Graph(e, day="Friday") 

361 >>> G.graph 

362 {'day': 'Friday'} 

363 

364 """ 

365 self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes 

366 self._node = self.node_dict_factory() # empty node attribute dict 

367 self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict 

368 # attempt to load graph with data 

369 if incoming_graph_data is not None: 

370 convert.to_networkx_graph(incoming_graph_data, create_using=self) 

371 # load graph attributes (must be after convert) 

372 self.graph.update(attr) 

373 

374 @cached_property 

375 def adj(self): 

376 """Graph adjacency object holding the neighbors of each node. 

377 

378 This object is a read-only dict-like structure with node keys 

379 and neighbor-dict values. The neighbor-dict is keyed by neighbor 

380 to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets 

381 the color of the edge `(3, 2)` to `"blue"`. 

382 

383 Iterating over G.adj behaves like a dict. Useful idioms include 

384 `for nbr, datadict in G.adj[n].items():`. 

385 

386 The neighbor information is also provided by subscripting the graph. 

387 So `for nbr, foovalue in G[node].data('foo', default=1):` works. 

388 

389 For directed graphs, `G.adj` holds outgoing (successor) info. 

390 """ 

391 return AdjacencyView(self._adj) 

392 

393 @property 

394 def name(self): 

395 """String identifier of the graph. 

396 

397 This graph attribute appears in the attribute dict G.graph 

398 keyed by the string `"name"`. as well as an attribute (technically 

399 a property) `G.name`. This is entirely user controlled. 

400 """ 

401 return self.graph.get("name", "") 

402 

403 @name.setter 

404 def name(self, s): 

405 self.graph["name"] = s 

406 

407 def __str__(self): 

408 """Returns a short summary of the graph. 

409 

410 Returns 

411 ------- 

412 info : string 

413 Graph information including the graph name (if any), graph type, and the 

414 number of nodes and edges. 

415 

416 Examples 

417 -------- 

418 >>> G = nx.Graph(name="foo") 

419 >>> str(G) 

420 "Graph named 'foo' with 0 nodes and 0 edges" 

421 

422 >>> G = nx.path_graph(3) 

423 >>> str(G) 

424 'Graph with 3 nodes and 2 edges' 

425 

426 """ 

427 return "".join( 

428 [ 

429 type(self).__name__, 

430 f" named {self.name!r}" if self.name else "", 

431 f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} edges", 

432 ] 

433 ) 

434 

435 def __iter__(self): 

436 """Iterate over the nodes. Use: 'for n in G'. 

437 

438 Returns 

439 ------- 

440 niter : iterator 

441 An iterator over all nodes in the graph. 

442 

443 Examples 

444 -------- 

445 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

446 >>> [n for n in G] 

447 [0, 1, 2, 3] 

448 >>> list(G) 

449 [0, 1, 2, 3] 

450 """ 

451 return iter(self._node) 

452 

453 def __contains__(self, n): 

454 """Returns True if n is a node, False otherwise. Use: 'n in G'. 

455 

456 Examples 

457 -------- 

458 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

459 >>> 1 in G 

460 True 

461 """ 

462 try: 

463 return n in self._node 

464 except TypeError: 

465 return False 

466 

467 def __len__(self): 

468 """Returns the number of nodes in the graph. Use: 'len(G)'. 

469 

470 Returns 

471 ------- 

472 nnodes : int 

473 The number of nodes in the graph. 

474 

475 See Also 

476 -------- 

477 number_of_nodes: identical method 

478 order: identical method 

479 

480 Examples 

481 -------- 

482 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

483 >>> len(G) 

484 4 

485 

486 """ 

487 return len(self._node) 

488 

489 def __getitem__(self, n): 

490 """Returns a dict of neighbors of node n. Use: 'G[n]'. 

491 

492 Parameters 

493 ---------- 

494 n : node 

495 A node in the graph. 

496 

497 Returns 

498 ------- 

499 adj_dict : dictionary 

500 The adjacency dictionary for nodes connected to n. 

501 

502 Notes 

503 ----- 

504 G[n] is the same as G.adj[n] and similar to G.neighbors(n) 

505 (which is an iterator over G.adj[n]) 

506 

507 Examples 

508 -------- 

509 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

510 >>> G[0] 

511 AtlasView({1: {}}) 

512 """ 

513 return self.adj[n] 

514 

515 def add_node(self, node_for_adding, **attr): 

516 """Add a single node `node_for_adding` and update node attributes. 

517 

518 Parameters 

519 ---------- 

520 node_for_adding : node 

521 A node can be any hashable Python object except None. 

522 attr : keyword arguments, optional 

523 Set or change node attributes using key=value. 

524 

525 See Also 

526 -------- 

527 add_nodes_from 

528 

529 Examples 

530 -------- 

531 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

532 >>> G.add_node(1) 

533 >>> G.add_node("Hello") 

534 >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)]) 

535 >>> G.add_node(K3) 

536 >>> G.number_of_nodes() 

537 3 

538 

539 Use keywords set/change node attributes: 

540 

541 >>> G.add_node(1, size=10) 

542 >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649)) 

543 

544 Notes 

545 ----- 

546 A hashable object is one that can be used as a key in a Python 

547 dictionary. This includes strings, numbers, tuples of strings 

548 and numbers, etc. 

549 

550 On many platforms hashable items also include mutables such as 

551 NetworkX Graphs, though one should be careful that the hash 

552 doesn't change on mutables. 

553 """ 

554 if node_for_adding not in self._node: 

555 if node_for_adding is None: 

556 raise ValueError("None cannot be a node") 

557 self._adj[node_for_adding] = self.adjlist_inner_dict_factory() 

558 attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory() 

559 attr_dict.update(attr) 

560 else: # update attr even if node already exists 

561 self._node[node_for_adding].update(attr) 

562 

563 def add_nodes_from(self, nodes_for_adding, **attr): 

564 """Add multiple nodes. 

565 

566 Parameters 

567 ---------- 

568 nodes_for_adding : iterable container 

569 A container of nodes (list, dict, set, etc.). 

570 OR 

571 A container of (node, attribute dict) tuples. 

572 Node attributes are updated using the attribute dict. 

573 attr : keyword arguments, optional (default= no attributes) 

574 Update attributes for all nodes in nodes. 

575 Node attributes specified in nodes as a tuple take 

576 precedence over attributes specified via keyword arguments. 

577 

578 See Also 

579 -------- 

580 add_node 

581 

582 Notes 

583 ----- 

584 When adding nodes from an iterator over the graph you are changing, 

585 a `RuntimeError` can be raised with message: 

586 `RuntimeError: dictionary changed size during iteration`. This 

587 happens when the graph's underlying dictionary is modified during 

588 iteration. To avoid this error, evaluate the iterator into a separate 

589 object, e.g. by using `list(iterator_of_nodes)`, and pass this 

590 object to `G.add_nodes_from`. 

591 

592 Examples 

593 -------- 

594 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

595 >>> G.add_nodes_from("Hello") 

596 >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)]) 

597 >>> G.add_nodes_from(K3) 

598 >>> sorted(G.nodes(), key=str) 

599 [0, 1, 2, 'H', 'e', 'l', 'o'] 

600 

601 Use keywords to update specific node attributes for every node. 

602 

603 >>> G.add_nodes_from([1, 2], size=10) 

604 >>> G.add_nodes_from([3, 4], weight=0.4) 

605 

606 Use (node, attrdict) tuples to update attributes for specific nodes. 

607 

608 >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})]) 

609 >>> G.nodes[1]["size"] 

610 11 

611 >>> H = nx.Graph() 

612 >>> H.add_nodes_from(G.nodes(data=True)) 

613 >>> H.nodes[1]["size"] 

614 11 

615 

616 Evaluate an iterator over a graph if using it to modify the same graph 

617 

618 >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)]) 

619 >>> # wrong way - will raise RuntimeError 

620 >>> # G.add_nodes_from(n + 1 for n in G.nodes) 

621 >>> # correct way 

622 >>> G.add_nodes_from(list(n + 1 for n in G.nodes)) 

623 """ 

624 for n in nodes_for_adding: 

625 try: 

626 newnode = n not in self._node 

627 newdict = attr 

628 except TypeError: 

629 n, ndict = n 

630 newnode = n not in self._node 

631 newdict = attr.copy() 

632 newdict.update(ndict) 

633 if newnode: 

634 if n is None: 

635 raise ValueError("None cannot be a node") 

636 self._adj[n] = self.adjlist_inner_dict_factory() 

637 self._node[n] = self.node_attr_dict_factory() 

638 self._node[n].update(newdict) 

639 

640 def remove_node(self, n): 

641 """Remove node n. 

642 

643 Removes the node n and all adjacent edges. 

644 Attempting to remove a nonexistent node will raise an exception. 

645 

646 Parameters 

647 ---------- 

648 n : node 

649 A node in the graph 

650 

651 Raises 

652 ------ 

653 NetworkXError 

654 If n is not in the graph. 

655 

656 See Also 

657 -------- 

658 remove_nodes_from 

659 

660 Examples 

661 -------- 

662 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc 

663 >>> list(G.edges) 

664 [(0, 1), (1, 2)] 

665 >>> G.remove_node(1) 

666 >>> list(G.edges) 

667 [] 

668 

669 """ 

670 adj = self._adj 

671 try: 

672 nbrs = list(adj[n]) # list handles self-loops (allows mutation) 

673 del self._node[n] 

674 except KeyError as err: # NetworkXError if n not in self 

675 raise NetworkXError(f"The node {n} is not in the graph.") from err 

676 for u in nbrs: 

677 del adj[u][n] # remove all edges n-u in graph 

678 del adj[n] # now remove node 

679 

680 def remove_nodes_from(self, nodes): 

681 """Remove multiple nodes. 

682 

683 Parameters 

684 ---------- 

685 nodes : iterable container 

686 A container of nodes (list, dict, set, etc.). If a node 

687 in the container is not in the graph it is silently 

688 ignored. 

689 

690 See Also 

691 -------- 

692 remove_node 

693 

694 Notes 

695 ----- 

696 When removing nodes from an iterator over the graph you are changing, 

697 a `RuntimeError` will be raised with message: 

698 `RuntimeError: dictionary changed size during iteration`. This 

699 happens when the graph's underlying dictionary is modified during 

700 iteration. To avoid this error, evaluate the iterator into a separate 

701 object, e.g. by using `list(iterator_of_nodes)`, and pass this 

702 object to `G.remove_nodes_from`. 

703 

704 Examples 

705 -------- 

706 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc 

707 >>> e = list(G.nodes) 

708 >>> e 

709 [0, 1, 2] 

710 >>> G.remove_nodes_from(e) 

711 >>> list(G.nodes) 

712 [] 

713 

714 Evaluate an iterator over a graph if using it to modify the same graph 

715 

716 >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)]) 

717 >>> # this command will fail, as the graph's dict is modified during iteration 

718 >>> # G.remove_nodes_from(n for n in G.nodes if n < 2) 

719 >>> # this command will work, since the dictionary underlying graph is not modified 

720 >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2)) 

721 """ 

722 adj = self._adj 

723 for n in nodes: 

724 try: 

725 del self._node[n] 

726 for u in list(adj[n]): # list handles self-loops 

727 del adj[u][n] # (allows mutation of dict in loop) 

728 del adj[n] 

729 except KeyError: 

730 pass 

731 

732 @cached_property 

733 def nodes(self): 

734 """A NodeView of the Graph as G.nodes or G.nodes(). 

735 

736 Can be used as `G.nodes` for data lookup and for set-like operations. 

737 Can also be used as `G.nodes(data='color', default=None)` to return a 

738 NodeDataView which reports specific node data but no set operations. 

739 It presents a dict-like interface as well with `G.nodes.items()` 

740 iterating over `(node, nodedata)` 2-tuples and `G.nodes[3]['foo']` 

741 providing the value of the `foo` attribute for node `3`. In addition, 

742 a view `G.nodes.data('foo')` provides a dict-like interface to the 

743 `foo` attribute of each node. `G.nodes.data('foo', default=1)` 

744 provides a default for nodes that do not have attribute `foo`. 

745 

746 Parameters 

747 ---------- 

748 data : string or bool, optional (default=False) 

749 The node attribute returned in 2-tuple (n, ddict[data]). 

750 If True, return entire node attribute dict as (n, ddict). 

751 If False, return just the nodes n. 

752 

753 default : value, optional (default=None) 

754 Value used for nodes that don't have the requested attribute. 

755 Only relevant if data is not True or False. 

756 

757 Returns 

758 ------- 

759 NodeView 

760 Allows set-like operations over the nodes as well as node 

761 attribute dict lookup and calling to get a NodeDataView. 

762 A NodeDataView iterates over `(n, data)` and has no set operations. 

763 A NodeView iterates over `n` and includes set operations. 

764 

765 When called, if data is False, an iterator over nodes. 

766 Otherwise an iterator of 2-tuples (node, attribute value) 

767 where the attribute is specified in `data`. 

768 If data is True then the attribute becomes the 

769 entire data dictionary. 

770 

771 Notes 

772 ----- 

773 If your node data is not needed, it is simpler and equivalent 

774 to use the expression ``for n in G``, or ``list(G)``. 

775 

776 Examples 

777 -------- 

778 There are two simple ways of getting a list of all nodes in the graph: 

779 

780 >>> G = nx.path_graph(3) 

781 >>> list(G.nodes) 

782 [0, 1, 2] 

783 >>> list(G) 

784 [0, 1, 2] 

785 

786 To get the node data along with the nodes: 

787 

788 >>> G.add_node(1, time="5pm") 

789 >>> G.nodes[0]["foo"] = "bar" 

790 >>> list(G.nodes(data=True)) 

791 [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})] 

792 >>> list(G.nodes.data()) 

793 [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})] 

794 

795 >>> list(G.nodes(data="foo")) 

796 [(0, 'bar'), (1, None), (2, None)] 

797 >>> list(G.nodes.data("foo")) 

798 [(0, 'bar'), (1, None), (2, None)] 

799 

800 >>> list(G.nodes(data="time")) 

801 [(0, None), (1, '5pm'), (2, None)] 

802 >>> list(G.nodes.data("time")) 

803 [(0, None), (1, '5pm'), (2, None)] 

804 

805 >>> list(G.nodes(data="time", default="Not Available")) 

806 [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')] 

807 >>> list(G.nodes.data("time", default="Not Available")) 

808 [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')] 

809 

810 If some of your nodes have an attribute and the rest are assumed 

811 to have a default attribute value you can create a dictionary 

812 from node/attribute pairs using the `default` keyword argument 

813 to guarantee the value is never None:: 

814 

815 >>> G = nx.Graph() 

816 >>> G.add_node(0) 

817 >>> G.add_node(1, weight=2) 

818 >>> G.add_node(2, weight=3) 

819 >>> dict(G.nodes(data="weight", default=1)) 

820 {0: 1, 1: 2, 2: 3} 

821 

822 """ 

823 return NodeView(self) 

824 

825 def number_of_nodes(self): 

826 """Returns the number of nodes in the graph. 

827 

828 Returns 

829 ------- 

830 nnodes : int 

831 The number of nodes in the graph. 

832 

833 See Also 

834 -------- 

835 order: identical method 

836 __len__: identical method 

837 

838 Examples 

839 -------- 

840 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc 

841 >>> G.number_of_nodes() 

842 3 

843 """ 

844 return len(self._node) 

845 

846 def order(self): 

847 """Returns the number of nodes in the graph. 

848 

849 Returns 

850 ------- 

851 nnodes : int 

852 The number of nodes in the graph. 

853 

854 See Also 

855 -------- 

856 number_of_nodes: identical method 

857 __len__: identical method 

858 

859 Examples 

860 -------- 

861 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc 

862 >>> G.order() 

863 3 

864 """ 

865 return len(self._node) 

866 

867 def has_node(self, n): 

868 """Returns True if the graph contains the node n. 

869 

870 Identical to `n in G` 

871 

872 Parameters 

873 ---------- 

874 n : node 

875 

876 Examples 

877 -------- 

878 >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc 

879 >>> G.has_node(0) 

880 True 

881 

882 It is more readable and simpler to use 

883 

884 >>> 0 in G 

885 True 

886 

887 """ 

888 try: 

889 return n in self._node 

890 except TypeError: 

891 return False 

892 

893 def add_edge(self, u_of_edge, v_of_edge, **attr): 

894 """Add an edge between u and v. 

895 

896 The nodes u and v will be automatically added if they are 

897 not already in the graph. 

898 

899 Edge attributes can be specified with keywords or by directly 

900 accessing the edge's attribute dictionary. See examples below. 

901 

902 Parameters 

903 ---------- 

904 u_of_edge, v_of_edge : nodes 

905 Nodes can be, for example, strings or numbers. 

906 Nodes must be hashable (and not None) Python objects. 

907 attr : keyword arguments, optional 

908 Edge data (or labels or objects) can be assigned using 

909 keyword arguments. 

910 

911 See Also 

912 -------- 

913 add_edges_from : add a collection of edges 

914 

915 Notes 

916 ----- 

917 Adding an edge that already exists updates the edge data. 

918 

919 Many NetworkX algorithms designed for weighted graphs use 

920 an edge attribute (by default `weight`) to hold a numerical value. 

921 

922 Examples 

923 -------- 

924 The following all add the edge e=(1, 2) to graph G: 

925 

926 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

927 >>> e = (1, 2) 

928 >>> G.add_edge(1, 2) # explicit two-node form 

929 >>> G.add_edge(*e) # single edge as tuple of two nodes 

930 >>> G.add_edges_from([(1, 2)]) # add edges from iterable container 

931 

932 Associate data to edges using keywords: 

933 

934 >>> G.add_edge(1, 2, weight=3) 

935 >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7) 

936 

937 For non-string attribute keys, use subscript notation. 

938 

939 >>> G.add_edge(1, 2) 

940 >>> G[1][2].update({0: 5}) 

941 >>> G.edges[1, 2].update({0: 5}) 

942 """ 

943 u, v = u_of_edge, v_of_edge 

944 # add nodes 

945 if u not in self._node: 

946 if u is None: 

947 raise ValueError("None cannot be a node") 

948 self._adj[u] = self.adjlist_inner_dict_factory() 

949 self._node[u] = self.node_attr_dict_factory() 

950 if v not in self._node: 

951 if v is None: 

952 raise ValueError("None cannot be a node") 

953 self._adj[v] = self.adjlist_inner_dict_factory() 

954 self._node[v] = self.node_attr_dict_factory() 

955 # add the edge 

956 datadict = self._adj[u].get(v, self.edge_attr_dict_factory()) 

957 datadict.update(attr) 

958 self._adj[u][v] = datadict 

959 self._adj[v][u] = datadict 

960 

961 def add_edges_from(self, ebunch_to_add, **attr): 

962 """Add all the edges in ebunch_to_add. 

963 

964 Parameters 

965 ---------- 

966 ebunch_to_add : container of edges 

967 Each edge given in the container will be added to the 

968 graph. The edges must be given as 2-tuples (u, v) or 

969 3-tuples (u, v, d) where d is a dictionary containing edge data. 

970 attr : keyword arguments, optional 

971 Edge data (or labels or objects) can be assigned using 

972 keyword arguments. 

973 

974 See Also 

975 -------- 

976 add_edge : add a single edge 

977 add_weighted_edges_from : convenient way to add weighted edges 

978 

979 Notes 

980 ----- 

981 Adding the same edge twice has no effect but any edge data 

982 will be updated when each duplicate edge is added. 

983 

984 Edge attributes specified in an ebunch take precedence over 

985 attributes specified via keyword arguments. 

986 

987 When adding edges from an iterator over the graph you are changing, 

988 a `RuntimeError` can be raised with message: 

989 `RuntimeError: dictionary changed size during iteration`. This 

990 happens when the graph's underlying dictionary is modified during 

991 iteration. To avoid this error, evaluate the iterator into a separate 

992 object, e.g. by using `list(iterator_of_edges)`, and pass this 

993 object to `G.add_edges_from`. 

994 

995 Examples 

996 -------- 

997 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

998 >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples 

999 >>> e = zip(range(0, 3), range(1, 4)) 

1000 >>> G.add_edges_from(e) # Add the path graph 0-1-2-3 

1001 

1002 Associate data to edges 

1003 

1004 >>> G.add_edges_from([(1, 2), (2, 3)], weight=3) 

1005 >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898") 

1006 

1007 Evaluate an iterator over a graph if using it to modify the same graph 

1008 

1009 >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)]) 

1010 >>> # Grow graph by one new node, adding edges to all existing nodes. 

1011 >>> # wrong way - will raise RuntimeError 

1012 >>> # G.add_edges_from(((5, n) for n in G.nodes)) 

1013 >>> # correct way - note that there will be no self-edge for node 5 

1014 >>> G.add_edges_from(list((5, n) for n in G.nodes)) 

1015 """ 

1016 for e in ebunch_to_add: 

1017 ne = len(e) 

1018 if ne == 3: 

1019 u, v, dd = e 

1020 elif ne == 2: 

1021 u, v = e 

1022 dd = {} # doesn't need edge_attr_dict_factory 

1023 else: 

1024 raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.") 

1025 if u not in self._node: 

1026 if u is None: 

1027 raise ValueError("None cannot be a node") 

1028 self._adj[u] = self.adjlist_inner_dict_factory() 

1029 self._node[u] = self.node_attr_dict_factory() 

1030 if v not in self._node: 

1031 if v is None: 

1032 raise ValueError("None cannot be a node") 

1033 self._adj[v] = self.adjlist_inner_dict_factory() 

1034 self._node[v] = self.node_attr_dict_factory() 

1035 datadict = self._adj[u].get(v, self.edge_attr_dict_factory()) 

1036 datadict.update(attr) 

1037 datadict.update(dd) 

1038 self._adj[u][v] = datadict 

1039 self._adj[v][u] = datadict 

1040 

1041 def add_weighted_edges_from(self, ebunch_to_add, weight="weight", **attr): 

1042 """Add weighted edges in `ebunch_to_add` with specified weight attr 

1043 

1044 Parameters 

1045 ---------- 

1046 ebunch_to_add : container of edges 

1047 Each edge given in the list or container will be added 

1048 to the graph. The edges must be given as 3-tuples (u, v, w) 

1049 where w is a number. 

1050 weight : string, optional (default= 'weight') 

1051 The attribute name for the edge weights to be added. 

1052 attr : keyword arguments, optional (default= no attributes) 

1053 Edge attributes to add/update for all edges. 

1054 

1055 See Also 

1056 -------- 

1057 add_edge : add a single edge 

1058 add_edges_from : add multiple edges 

1059 

1060 Notes 

1061 ----- 

1062 Adding the same edge twice for Graph/DiGraph simply updates 

1063 the edge data. For MultiGraph/MultiDiGraph, duplicate edges 

1064 are stored. 

1065 

1066 When adding edges from an iterator over the graph you are changing, 

1067 a `RuntimeError` can be raised with message: 

1068 `RuntimeError: dictionary changed size during iteration`. This 

1069 happens when the graph's underlying dictionary is modified during 

1070 iteration. To avoid this error, evaluate the iterator into a separate 

1071 object, e.g. by using `list(iterator_of_edges)`, and pass this 

1072 object to `G.add_weighted_edges_from`. 

1073 

1074 Examples 

1075 -------- 

1076 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

1077 >>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)]) 

1078 

1079 Evaluate an iterator over edges before passing it 

1080 

1081 >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)]) 

1082 >>> weight = 0.1 

1083 >>> # Grow graph by one new node, adding edges to all existing nodes. 

1084 >>> # wrong way - will raise RuntimeError 

1085 >>> # G.add_weighted_edges_from(((5, n, weight) for n in G.nodes)) 

1086 >>> # correct way - note that there will be no self-edge for node 5 

1087 >>> G.add_weighted_edges_from(list((5, n, weight) for n in G.nodes)) 

1088 """ 

1089 self.add_edges_from(((u, v, {weight: d}) for u, v, d in ebunch_to_add), **attr) 

1090 

1091 def remove_edge(self, u, v): 

1092 """Remove the edge between u and v. 

1093 

1094 Parameters 

1095 ---------- 

1096 u, v : nodes 

1097 Remove the edge between nodes u and v. 

1098 

1099 Raises 

1100 ------ 

1101 NetworkXError 

1102 If there is not an edge between u and v. 

1103 

1104 See Also 

1105 -------- 

1106 remove_edges_from : remove a collection of edges 

1107 

1108 Examples 

1109 -------- 

1110 >>> G = nx.path_graph(4) # or DiGraph, etc 

1111 >>> G.remove_edge(0, 1) 

1112 >>> e = (1, 2) 

1113 >>> G.remove_edge(*e) # unpacks e from an edge tuple 

1114 >>> e = (2, 3, {"weight": 7}) # an edge with attribute data 

1115 >>> G.remove_edge(*e[:2]) # select first part of edge tuple 

1116 """ 

1117 try: 

1118 del self._adj[u][v] 

1119 if u != v: # self-loop needs only one entry removed 

1120 del self._adj[v][u] 

1121 except KeyError as err: 

1122 raise NetworkXError(f"The edge {u}-{v} is not in the graph") from err 

1123 

1124 def remove_edges_from(self, ebunch): 

1125 """Remove all edges specified in ebunch. 

1126 

1127 Parameters 

1128 ---------- 

1129 ebunch: list or container of edge tuples 

1130 Each edge given in the list or container will be removed 

1131 from the graph. The edges can be: 

1132 

1133 - 2-tuples (u, v) edge between u and v. 

1134 - 3-tuples (u, v, k) where k is ignored. 

1135 

1136 See Also 

1137 -------- 

1138 remove_edge : remove a single edge 

1139 

1140 Notes 

1141 ----- 

1142 Will fail silently if an edge in ebunch is not in the graph. 

1143 

1144 Examples 

1145 -------- 

1146 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1147 >>> ebunch = [(1, 2), (2, 3)] 

1148 >>> G.remove_edges_from(ebunch) 

1149 """ 

1150 adj = self._adj 

1151 for e in ebunch: 

1152 u, v = e[:2] # ignore edge data if present 

1153 if u in adj and v in adj[u]: 

1154 del adj[u][v] 

1155 if u != v: # self loop needs only one entry removed 

1156 del adj[v][u] 

1157 

1158 def update(self, edges=None, nodes=None): 

1159 """Update the graph using nodes/edges/graphs as input. 

1160 

1161 Like dict.update, this method takes a graph as input, adding the 

1162 graph's nodes and edges to this graph. It can also take two inputs: 

1163 edges and nodes. Finally it can take either edges or nodes. 

1164 To specify only nodes the keyword `nodes` must be used. 

1165 

1166 The collections of edges and nodes are treated similarly to 

1167 the add_edges_from/add_nodes_from methods. When iterated, they 

1168 should yield 2-tuples (u, v) or 3-tuples (u, v, datadict). 

1169 

1170 Parameters 

1171 ---------- 

1172 edges : Graph object, collection of edges, or None 

1173 The first parameter can be a graph or some edges. If it has 

1174 attributes `nodes` and `edges`, then it is taken to be a 

1175 Graph-like object and those attributes are used as collections 

1176 of nodes and edges to be added to the graph. 

1177 If the first parameter does not have those attributes, it is 

1178 treated as a collection of edges and added to the graph. 

1179 If the first argument is None, no edges are added. 

1180 nodes : collection of nodes, or None 

1181 The second parameter is treated as a collection of nodes 

1182 to be added to the graph unless it is None. 

1183 If `edges is None` and `nodes is None` an exception is raised. 

1184 If the first parameter is a Graph, then `nodes` is ignored. 

1185 

1186 Examples 

1187 -------- 

1188 >>> G = nx.path_graph(5) 

1189 >>> G.update(nx.complete_graph(range(4, 10))) 

1190 >>> from itertools import combinations 

1191 >>> edges = ( 

1192 ... (u, v, {"power": u * v}) 

1193 ... for u, v in combinations(range(10, 20), 2) 

1194 ... if u * v < 225 

1195 ... ) 

1196 >>> nodes = [1000] # for singleton, use a container 

1197 >>> G.update(edges, nodes) 

1198 

1199 Notes 

1200 ----- 

1201 It you want to update the graph using an adjacency structure 

1202 it is straightforward to obtain the edges/nodes from adjacency. 

1203 The following examples provide common cases, your adjacency may 

1204 be slightly different and require tweaks of these examples:: 

1205 

1206 >>> # dict-of-set/list/tuple 

1207 >>> adj = {1: {2, 3}, 2: {1, 3}, 3: {1, 2}} 

1208 >>> e = [(u, v) for u, nbrs in adj.items() for v in nbrs] 

1209 >>> G.update(edges=e, nodes=adj) 

1210 

1211 >>> DG = nx.DiGraph() 

1212 >>> # dict-of-dict-of-attribute 

1213 >>> adj = {1: {2: 1.3, 3: 0.7}, 2: {1: 1.4}, 3: {1: 0.7}} 

1214 >>> e = [ 

1215 ... (u, v, {"weight": d}) 

1216 ... for u, nbrs in adj.items() 

1217 ... for v, d in nbrs.items() 

1218 ... ] 

1219 >>> DG.update(edges=e, nodes=adj) 

1220 

1221 >>> # dict-of-dict-of-dict 

1222 >>> adj = {1: {2: {"weight": 1.3}, 3: {"color": 0.7, "weight": 1.2}}} 

1223 >>> e = [ 

1224 ... (u, v, {"weight": d}) 

1225 ... for u, nbrs in adj.items() 

1226 ... for v, d in nbrs.items() 

1227 ... ] 

1228 >>> DG.update(edges=e, nodes=adj) 

1229 

1230 >>> # predecessor adjacency (dict-of-set) 

1231 >>> pred = {1: {2, 3}, 2: {3}, 3: {3}} 

1232 >>> e = [(v, u) for u, nbrs in pred.items() for v in nbrs] 

1233 

1234 >>> # MultiGraph dict-of-dict-of-dict-of-attribute 

1235 >>> MDG = nx.MultiDiGraph() 

1236 >>> adj = { 

1237 ... 1: {2: {0: {"weight": 1.3}, 1: {"weight": 1.2}}}, 

1238 ... 3: {2: {0: {"weight": 0.7}}}, 

1239 ... } 

1240 >>> e = [ 

1241 ... (u, v, ekey, d) 

1242 ... for u, nbrs in adj.items() 

1243 ... for v, keydict in nbrs.items() 

1244 ... for ekey, d in keydict.items() 

1245 ... ] 

1246 >>> MDG.update(edges=e) 

1247 

1248 See Also 

1249 -------- 

1250 add_edges_from: add multiple edges to a graph 

1251 add_nodes_from: add multiple nodes to a graph 

1252 """ 

1253 if edges is not None: 

1254 if nodes is not None: 

1255 self.add_nodes_from(nodes) 

1256 self.add_edges_from(edges) 

1257 else: 

1258 # check if edges is a Graph object 

1259 try: 

1260 graph_nodes = edges.nodes 

1261 graph_edges = edges.edges 

1262 except AttributeError: 

1263 # edge not Graph-like 

1264 self.add_edges_from(edges) 

1265 else: # edges is Graph-like 

1266 self.add_nodes_from(graph_nodes.data()) 

1267 self.add_edges_from(graph_edges.data()) 

1268 self.graph.update(edges.graph) 

1269 elif nodes is not None: 

1270 self.add_nodes_from(nodes) 

1271 else: 

1272 raise NetworkXError("update needs nodes or edges input") 

1273 

1274 def has_edge(self, u, v): 

1275 """Returns True if the edge (u, v) is in the graph. 

1276 

1277 This is the same as `v in G[u]` without KeyError exceptions. 

1278 

1279 Parameters 

1280 ---------- 

1281 u, v : nodes 

1282 Nodes can be, for example, strings or numbers. 

1283 Nodes must be hashable (and not None) Python objects. 

1284 

1285 Returns 

1286 ------- 

1287 edge_ind : bool 

1288 True if edge is in the graph, False otherwise. 

1289 

1290 Examples 

1291 -------- 

1292 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1293 >>> G.has_edge(0, 1) # using two nodes 

1294 True 

1295 >>> e = (0, 1) 

1296 >>> G.has_edge(*e) # e is a 2-tuple (u, v) 

1297 True 

1298 >>> e = (0, 1, {"weight": 7}) 

1299 >>> G.has_edge(*e[:2]) # e is a 3-tuple (u, v, data_dictionary) 

1300 True 

1301 

1302 The following syntax are equivalent: 

1303 

1304 >>> G.has_edge(0, 1) 

1305 True 

1306 >>> 1 in G[0] # though this gives KeyError if 0 not in G 

1307 True 

1308 

1309 """ 

1310 try: 

1311 return v in self._adj[u] 

1312 except KeyError: 

1313 return False 

1314 

1315 def neighbors(self, n): 

1316 """Returns an iterator over all neighbors of node n. 

1317 

1318 This is identical to `iter(G[n])` 

1319 

1320 Parameters 

1321 ---------- 

1322 n : node 

1323 A node in the graph 

1324 

1325 Returns 

1326 ------- 

1327 neighbors : iterator 

1328 An iterator over all neighbors of node n 

1329 

1330 Raises 

1331 ------ 

1332 NetworkXError 

1333 If the node n is not in the graph. 

1334 

1335 Examples 

1336 -------- 

1337 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1338 >>> [n for n in G.neighbors(0)] 

1339 [1] 

1340 

1341 Notes 

1342 ----- 

1343 Alternate ways to access the neighbors are ``G.adj[n]`` or ``G[n]``: 

1344 

1345 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

1346 >>> G.add_edge("a", "b", weight=7) 

1347 >>> G["a"] 

1348 AtlasView({'b': {'weight': 7}}) 

1349 >>> G = nx.path_graph(4) 

1350 >>> [n for n in G[0]] 

1351 [1] 

1352 """ 

1353 try: 

1354 return iter(self._adj[n]) 

1355 except KeyError as err: 

1356 raise NetworkXError(f"The node {n} is not in the graph.") from err 

1357 

1358 @cached_property 

1359 def edges(self): 

1360 """An EdgeView of the Graph as G.edges or G.edges(). 

1361 

1362 edges(self, nbunch=None, data=False, default=None) 

1363 

1364 The EdgeView provides set-like operations on the edge-tuples 

1365 as well as edge attribute lookup. When called, it also provides 

1366 an EdgeDataView object which allows control of access to edge 

1367 attributes (but does not provide set-like operations). 

1368 Hence, `G.edges[u, v]['color']` provides the value of the color 

1369 attribute for edge `(u, v)` while 

1370 `for (u, v, c) in G.edges.data('color', default='red'):` 

1371 iterates through all the edges yielding the color attribute 

1372 with default `'red'` if no color attribute exists. 

1373 

1374 Parameters 

1375 ---------- 

1376 nbunch : single node, container, or all nodes (default= all nodes) 

1377 The view will only report edges from these nodes. 

1378 data : string or bool, optional (default=False) 

1379 The edge attribute returned in 3-tuple (u, v, ddict[data]). 

1380 If True, return edge attribute dict in 3-tuple (u, v, ddict). 

1381 If False, return 2-tuple (u, v). 

1382 default : value, optional (default=None) 

1383 Value used for edges that don't have the requested attribute. 

1384 Only relevant if data is not True or False. 

1385 

1386 Returns 

1387 ------- 

1388 edges : EdgeView 

1389 A view of edge attributes, usually it iterates over (u, v) 

1390 or (u, v, d) tuples of edges, but can also be used for 

1391 attribute lookup as `edges[u, v]['foo']`. 

1392 

1393 Notes 

1394 ----- 

1395 Nodes in nbunch that are not in the graph will be (quietly) ignored. 

1396 For directed graphs this returns the out-edges. 

1397 

1398 Examples 

1399 -------- 

1400 >>> G = nx.path_graph(3) # or MultiGraph, etc 

1401 >>> G.add_edge(2, 3, weight=5) 

1402 >>> [e for e in G.edges] 

1403 [(0, 1), (1, 2), (2, 3)] 

1404 >>> G.edges.data() # default data is {} (empty dict) 

1405 EdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})]) 

1406 >>> G.edges.data("weight", default=1) 

1407 EdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)]) 

1408 >>> G.edges([0, 3]) # only edges from these nodes 

1409 EdgeDataView([(0, 1), (3, 2)]) 

1410 >>> G.edges(0) # only edges from node 0 

1411 EdgeDataView([(0, 1)]) 

1412 """ 

1413 return EdgeView(self) 

1414 

1415 def get_edge_data(self, u, v, default=None): 

1416 """Returns the attribute dictionary associated with edge (u, v). 

1417 

1418 This is identical to `G[u][v]` except the default is returned 

1419 instead of an exception if the edge doesn't exist. 

1420 

1421 Parameters 

1422 ---------- 

1423 u, v : nodes 

1424 default: any Python object (default=None) 

1425 Value to return if the edge (u, v) is not found. 

1426 

1427 Returns 

1428 ------- 

1429 edge_dict : dictionary 

1430 The edge attribute dictionary. 

1431 

1432 Examples 

1433 -------- 

1434 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1435 >>> G[0][1] 

1436 {} 

1437 

1438 Warning: Assigning to `G[u][v]` is not permitted. 

1439 But it is safe to assign attributes `G[u][v]['foo']` 

1440 

1441 >>> G[0][1]["weight"] = 7 

1442 >>> G[0][1]["weight"] 

1443 7 

1444 >>> G[1][0]["weight"] 

1445 7 

1446 

1447 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1448 >>> G.get_edge_data(0, 1) # default edge data is {} 

1449 {} 

1450 >>> e = (0, 1) 

1451 >>> G.get_edge_data(*e) # tuple form 

1452 {} 

1453 >>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0 

1454 0 

1455 """ 

1456 try: 

1457 return self._adj[u][v] 

1458 except KeyError: 

1459 return default 

1460 

1461 def adjacency(self): 

1462 """Returns an iterator over (node, adjacency dict) tuples for all nodes. 

1463 

1464 For directed graphs, only outgoing neighbors/adjacencies are included. 

1465 

1466 Returns 

1467 ------- 

1468 adj_iter : iterator 

1469 An iterator over (node, adjacency dictionary) for all nodes in 

1470 the graph. 

1471 

1472 Examples 

1473 -------- 

1474 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1475 >>> [(n, nbrdict) for n, nbrdict in G.adjacency()] 

1476 [(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})] 

1477 

1478 """ 

1479 return iter(self._adj.items()) 

1480 

1481 @cached_property 

1482 def degree(self): 

1483 """A DegreeView for the Graph as G.degree or G.degree(). 

1484 

1485 The node degree is the number of edges adjacent to the node. 

1486 The weighted node degree is the sum of the edge weights for 

1487 edges incident to that node. 

1488 

1489 This object provides an iterator for (node, degree) as well as 

1490 lookup for the degree for a single node. 

1491 

1492 Parameters 

1493 ---------- 

1494 nbunch : single node, container, or all nodes (default= all nodes) 

1495 The view will only report edges incident to these nodes. 

1496 

1497 weight : string or None, optional (default=None) 

1498 The name of an edge attribute that holds the numerical value used 

1499 as a weight. If None, then each edge has weight 1. 

1500 The degree is the sum of the edge weights adjacent to the node. 

1501 

1502 Returns 

1503 ------- 

1504 DegreeView or int 

1505 If multiple nodes are requested (the default), returns a `DegreeView` 

1506 mapping nodes to their degree. 

1507 If a single node is requested, returns the degree of the node as an integer. 

1508 

1509 Examples 

1510 -------- 

1511 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1512 >>> G.degree[0] # node 0 has degree 1 

1513 1 

1514 >>> list(G.degree([0, 1, 2])) 

1515 [(0, 1), (1, 2), (2, 2)] 

1516 """ 

1517 return DegreeView(self) 

1518 

1519 def clear(self): 

1520 """Remove all nodes and edges from the graph. 

1521 

1522 This also removes the name, and all graph, node, and edge attributes. 

1523 

1524 Examples 

1525 -------- 

1526 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1527 >>> G.clear() 

1528 >>> list(G.nodes) 

1529 [] 

1530 >>> list(G.edges) 

1531 [] 

1532 

1533 """ 

1534 self._adj.clear() 

1535 self._node.clear() 

1536 self.graph.clear() 

1537 

1538 def clear_edges(self): 

1539 """Remove all edges from the graph without altering nodes. 

1540 

1541 Examples 

1542 -------- 

1543 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1544 >>> G.clear_edges() 

1545 >>> list(G.nodes) 

1546 [0, 1, 2, 3] 

1547 >>> list(G.edges) 

1548 [] 

1549 """ 

1550 for neighbours_dict in self._adj.values(): 

1551 neighbours_dict.clear() 

1552 

1553 def is_multigraph(self): 

1554 """Returns True if graph is a multigraph, False otherwise.""" 

1555 return False 

1556 

1557 def is_directed(self): 

1558 """Returns True if graph is directed, False otherwise.""" 

1559 return False 

1560 

1561 def copy(self, as_view=False): 

1562 """Returns a copy of the graph. 

1563 

1564 The copy method by default returns an independent shallow copy 

1565 of the graph and attributes. That is, if an attribute is a 

1566 container, that container is shared by the original an the copy. 

1567 Use Python's `copy.deepcopy` for new containers. 

1568 

1569 If `as_view` is True then a view is returned instead of a copy. 

1570 

1571 Notes 

1572 ----- 

1573 All copies reproduce the graph structure, but data attributes 

1574 may be handled in different ways. There are four types of copies 

1575 of a graph that people might want. 

1576 

1577 Deepcopy -- A "deepcopy" copies the graph structure as well as 

1578 all data attributes and any objects they might contain. 

1579 The entire graph object is new so that changes in the copy 

1580 do not affect the original object. (see Python's copy.deepcopy) 

1581 

1582 Data Reference (Shallow) -- For a shallow copy the graph structure 

1583 is copied but the edge, node and graph attribute dicts are 

1584 references to those in the original graph. This saves 

1585 time and memory but could cause confusion if you change an attribute 

1586 in one graph and it changes the attribute in the other. 

1587 NetworkX does not provide this level of shallow copy. 

1588 

1589 Independent Shallow -- This copy creates new independent attribute 

1590 dicts and then does a shallow copy of the attributes. That is, any 

1591 attributes that are containers are shared between the new graph 

1592 and the original. This is exactly what `dict.copy()` provides. 

1593 You can obtain this style copy using: 

1594 

1595 >>> G = nx.path_graph(5) 

1596 >>> H = G.copy() 

1597 >>> H = G.copy(as_view=False) 

1598 >>> H = nx.Graph(G) 

1599 >>> H = G.__class__(G) 

1600 

1601 Fresh Data -- For fresh data, the graph structure is copied while 

1602 new empty data attribute dicts are created. The resulting graph 

1603 is independent of the original and it has no edge, node or graph 

1604 attributes. Fresh copies are not enabled. Instead use: 

1605 

1606 >>> H = G.__class__() 

1607 >>> H.add_nodes_from(G) 

1608 >>> H.add_edges_from(G.edges) 

1609 

1610 View -- Inspired by dict-views, graph-views act like read-only 

1611 versions of the original graph, providing a copy of the original 

1612 structure without requiring any memory for copying the information. 

1613 

1614 See the Python copy module for more information on shallow 

1615 and deep copies, https://docs.python.org/3/library/copy.html. 

1616 

1617 Parameters 

1618 ---------- 

1619 as_view : bool, optional (default=False) 

1620 If True, the returned graph-view provides a read-only view 

1621 of the original graph without actually copying any data. 

1622 

1623 Returns 

1624 ------- 

1625 G : Graph 

1626 A copy of the graph. 

1627 

1628 See Also 

1629 -------- 

1630 to_directed: return a directed copy of the graph. 

1631 

1632 Examples 

1633 -------- 

1634 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1635 >>> H = G.copy() 

1636 

1637 """ 

1638 if as_view is True: 

1639 return nx.graphviews.generic_graph_view(self) 

1640 G = self.__class__() 

1641 G.graph.update(self.graph) 

1642 G.add_nodes_from((n, d.copy()) for n, d in self._node.items()) 

1643 G.add_edges_from( 

1644 (u, v, datadict.copy()) 

1645 for u, nbrs in self._adj.items() 

1646 for v, datadict in nbrs.items() 

1647 ) 

1648 return G 

1649 

1650 def to_directed(self, as_view=False): 

1651 """Returns a directed representation of the graph. 

1652 

1653 Returns 

1654 ------- 

1655 G : DiGraph 

1656 A directed graph with the same name, same nodes, and with 

1657 each edge (u, v, data) replaced by two directed edges 

1658 (u, v, data) and (v, u, data). 

1659 

1660 Notes 

1661 ----- 

1662 This returns a "deepcopy" of the edge, node, and 

1663 graph attributes which attempts to completely copy 

1664 all of the data and references. 

1665 

1666 This is in contrast to the similar D=DiGraph(G) which returns a 

1667 shallow copy of the data. 

1668 

1669 See the Python copy module for more information on shallow 

1670 and deep copies, https://docs.python.org/3/library/copy.html. 

1671 

1672 Warning: If you have subclassed Graph to use dict-like objects 

1673 in the data structure, those changes do not transfer to the 

1674 DiGraph created by this method. 

1675 

1676 Examples 

1677 -------- 

1678 >>> G = nx.Graph() # or MultiGraph, etc 

1679 >>> G.add_edge(0, 1) 

1680 >>> H = G.to_directed() 

1681 >>> list(H.edges) 

1682 [(0, 1), (1, 0)] 

1683 

1684 If already directed, return a (deep) copy 

1685 

1686 >>> G = nx.DiGraph() # or MultiDiGraph, etc 

1687 >>> G.add_edge(0, 1) 

1688 >>> H = G.to_directed() 

1689 >>> list(H.edges) 

1690 [(0, 1)] 

1691 """ 

1692 graph_class = self.to_directed_class() 

1693 if as_view is True: 

1694 return nx.graphviews.generic_graph_view(self, graph_class) 

1695 # deepcopy when not a view 

1696 G = graph_class() 

1697 G.graph.update(deepcopy(self.graph)) 

1698 G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) 

1699 G.add_edges_from( 

1700 (u, v, deepcopy(data)) 

1701 for u, nbrs in self._adj.items() 

1702 for v, data in nbrs.items() 

1703 ) 

1704 return G 

1705 

1706 def to_undirected(self, as_view=False): 

1707 """Returns an undirected copy of the graph. 

1708 

1709 Parameters 

1710 ---------- 

1711 as_view : bool (optional, default=False) 

1712 If True return a view of the original undirected graph. 

1713 

1714 Returns 

1715 ------- 

1716 G : Graph/MultiGraph 

1717 A deepcopy of the graph. 

1718 

1719 See Also 

1720 -------- 

1721 Graph, copy, add_edge, add_edges_from 

1722 

1723 Notes 

1724 ----- 

1725 This returns a "deepcopy" of the edge, node, and 

1726 graph attributes which attempts to completely copy 

1727 all of the data and references. 

1728 

1729 This is in contrast to the similar `G = nx.DiGraph(D)` which returns a 

1730 shallow copy of the data. 

1731 

1732 See the Python copy module for more information on shallow 

1733 and deep copies, https://docs.python.org/3/library/copy.html. 

1734 

1735 Warning: If you have subclassed DiGraph to use dict-like objects 

1736 in the data structure, those changes do not transfer to the 

1737 Graph created by this method. 

1738 

1739 Examples 

1740 -------- 

1741 >>> G = nx.path_graph(2) # or MultiGraph, etc 

1742 >>> H = G.to_directed() 

1743 >>> list(H.edges) 

1744 [(0, 1), (1, 0)] 

1745 >>> G2 = H.to_undirected() 

1746 >>> list(G2.edges) 

1747 [(0, 1)] 

1748 """ 

1749 graph_class = self.to_undirected_class() 

1750 if as_view is True: 

1751 return nx.graphviews.generic_graph_view(self, graph_class) 

1752 # deepcopy when not a view 

1753 G = graph_class() 

1754 G.graph.update(deepcopy(self.graph)) 

1755 G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) 

1756 G.add_edges_from( 

1757 (u, v, deepcopy(d)) 

1758 for u, nbrs in self._adj.items() 

1759 for v, d in nbrs.items() 

1760 ) 

1761 return G 

1762 

1763 def subgraph(self, nodes): 

1764 """Returns a SubGraph view of the subgraph induced on `nodes`. 

1765 

1766 The induced subgraph of the graph contains the nodes in `nodes` 

1767 and the edges between those nodes. 

1768 

1769 Parameters 

1770 ---------- 

1771 nodes : list, iterable 

1772 A container of nodes which will be iterated through once. 

1773 

1774 Returns 

1775 ------- 

1776 G : SubGraph View 

1777 A subgraph view of the graph. The graph structure cannot be 

1778 changed but node/edge attributes can and are shared with the 

1779 original graph. 

1780 

1781 Notes 

1782 ----- 

1783 The graph, edge and node attributes are shared with the original graph. 

1784 Changes to the graph structure is ruled out by the view, but changes 

1785 to attributes are reflected in the original graph. 

1786 

1787 To create a subgraph with its own copy of the edge/node attributes use: 

1788 G.subgraph(nodes).copy() 

1789 

1790 For an inplace reduction of a graph to a subgraph you can remove nodes: 

1791 G.remove_nodes_from([n for n in G if n not in set(nodes)]) 

1792 

1793 Subgraph views are sometimes NOT what you want. In most cases where 

1794 you want to do more than simply look at the induced edges, it makes 

1795 more sense to just create the subgraph as its own graph with code like: 

1796 

1797 :: 

1798 

1799 # Create a subgraph SG based on a (possibly multigraph) G 

1800 SG = G.__class__() 

1801 SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc) 

1802 if SG.is_multigraph(): 

1803 SG.add_edges_from((n, nbr, key, d) 

1804 for n, nbrs in G.adj.items() if n in largest_wcc 

1805 for nbr, keydict in nbrs.items() if nbr in largest_wcc 

1806 for key, d in keydict.items()) 

1807 else: 

1808 SG.add_edges_from((n, nbr, d) 

1809 for n, nbrs in G.adj.items() if n in largest_wcc 

1810 for nbr, d in nbrs.items() if nbr in largest_wcc) 

1811 SG.graph.update(G.graph) 

1812 

1813 Examples 

1814 -------- 

1815 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1816 >>> H = G.subgraph([0, 1, 2]) 

1817 >>> list(H.edges) 

1818 [(0, 1), (1, 2)] 

1819 """ 

1820 induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes)) 

1821 # if already a subgraph, don't make a chain 

1822 subgraph = nx.subgraph_view 

1823 if hasattr(self, "_NODE_OK"): 

1824 return subgraph( 

1825 self._graph, filter_node=induced_nodes, filter_edge=self._EDGE_OK 

1826 ) 

1827 return subgraph(self, filter_node=induced_nodes) 

1828 

1829 def edge_subgraph(self, edges): 

1830 """Returns the subgraph induced by the specified edges. 

1831 

1832 The induced subgraph contains each edge in `edges` and each 

1833 node incident to any one of those edges. 

1834 

1835 Parameters 

1836 ---------- 

1837 edges : iterable 

1838 An iterable of edges in this graph. 

1839 

1840 Returns 

1841 ------- 

1842 G : Graph 

1843 An edge-induced subgraph of this graph with the same edge 

1844 attributes. 

1845 

1846 Notes 

1847 ----- 

1848 The graph, edge, and node attributes in the returned subgraph 

1849 view are references to the corresponding attributes in the original 

1850 graph. The view is read-only. 

1851 

1852 To create a full graph version of the subgraph with its own copy 

1853 of the edge or node attributes, use:: 

1854 

1855 G.edge_subgraph(edges).copy() 

1856 

1857 Examples 

1858 -------- 

1859 >>> G = nx.path_graph(5) 

1860 >>> H = G.edge_subgraph([(0, 1), (3, 4)]) 

1861 >>> list(H.nodes) 

1862 [0, 1, 3, 4] 

1863 >>> list(H.edges) 

1864 [(0, 1), (3, 4)] 

1865 

1866 """ 

1867 return nx.edge_subgraph(self, edges) 

1868 

1869 def size(self, weight=None): 

1870 """Returns the number of edges or total of all edge weights. 

1871 

1872 Parameters 

1873 ---------- 

1874 weight : string or None, optional (default=None) 

1875 The edge attribute that holds the numerical value used 

1876 as a weight. If None, then each edge has weight 1. 

1877 

1878 Returns 

1879 ------- 

1880 size : numeric 

1881 The number of edges or 

1882 (if weight keyword is provided) the total weight sum. 

1883 

1884 If weight is None, returns an int. Otherwise a float 

1885 (or more general numeric if the weights are more general). 

1886 

1887 See Also 

1888 -------- 

1889 number_of_edges 

1890 

1891 Examples 

1892 -------- 

1893 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc 

1894 >>> G.size() 

1895 3 

1896 

1897 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc 

1898 >>> G.add_edge("a", "b", weight=2) 

1899 >>> G.add_edge("b", "c", weight=4) 

1900 >>> G.size() 

1901 2 

1902 >>> G.size(weight="weight") 

1903 6.0 

1904 """ 

1905 s = sum(d for v, d in self.degree(weight=weight)) 

1906 # If `weight` is None, the sum of the degrees is guaranteed to be 

1907 # even, so we can perform integer division and hence return an 

1908 # integer. Otherwise, the sum of the weighted degrees is not 

1909 # guaranteed to be an integer, so we perform "real" division. 

1910 return s // 2 if weight is None else s / 2 

1911 

1912 def number_of_edges(self, u=None, v=None): 

1913 """Returns the number of edges between two nodes. 

1914 

1915 Parameters 

1916 ---------- 

1917 u, v : nodes, optional (default=all edges) 

1918 If u and v are specified, return the number of edges between 

1919 u and v. Otherwise return the total number of all edges. 

1920 

1921 Returns 

1922 ------- 

1923 nedges : int 

1924 The number of edges in the graph. If nodes `u` and `v` are 

1925 specified return the number of edges between those nodes. If 

1926 the graph is directed, this only returns the number of edges 

1927 from `u` to `v`. 

1928 

1929 See Also 

1930 -------- 

1931 size 

1932 

1933 Examples 

1934 -------- 

1935 For undirected graphs, this method counts the total number of 

1936 edges in the graph: 

1937 

1938 >>> G = nx.path_graph(4) 

1939 >>> G.number_of_edges() 

1940 3 

1941 

1942 If you specify two nodes, this counts the total number of edges 

1943 joining the two nodes: 

1944 

1945 >>> G.number_of_edges(0, 1) 

1946 1 

1947 

1948 For directed graphs, this method can count the total number of 

1949 directed edges from `u` to `v`: 

1950 

1951 >>> G = nx.DiGraph() 

1952 >>> G.add_edge(0, 1) 

1953 >>> G.add_edge(1, 0) 

1954 >>> G.number_of_edges(0, 1) 

1955 1 

1956 

1957 """ 

1958 if u is None: 

1959 return int(self.size()) 

1960 if v in self._adj[u]: 

1961 return 1 

1962 return 0 

1963 

1964 def nbunch_iter(self, nbunch=None): 

1965 """Returns an iterator over nodes contained in nbunch that are 

1966 also in the graph. 

1967 

1968 The nodes in nbunch are checked for membership in the graph 

1969 and if not are silently ignored. 

1970 

1971 Parameters 

1972 ---------- 

1973 nbunch : single node, container, or all nodes (default= all nodes) 

1974 The view will only report edges incident to these nodes. 

1975 

1976 Returns 

1977 ------- 

1978 niter : iterator 

1979 An iterator over nodes in nbunch that are also in the graph. 

1980 If nbunch is None, iterate over all nodes in the graph. 

1981 

1982 Raises 

1983 ------ 

1984 NetworkXError 

1985 If nbunch is not a node or sequence of nodes. 

1986 If a node in nbunch is not hashable. 

1987 

1988 See Also 

1989 -------- 

1990 Graph.__iter__ 

1991 

1992 Notes 

1993 ----- 

1994 When nbunch is an iterator, the returned iterator yields values 

1995 directly from nbunch, becoming exhausted when nbunch is exhausted. 

1996 

1997 To test whether nbunch is a single node, one can use 

1998 "if nbunch in self:", even after processing with this routine. 

1999 

2000 If nbunch is not a node or a (possibly empty) sequence/iterator 

2001 or None, a :exc:`NetworkXError` is raised. Also, if any object in 

2002 nbunch is not hashable, a :exc:`NetworkXError` is raised. 

2003 """ 

2004 if nbunch is None: # include all nodes via iterator 

2005 bunch = iter(self._adj) 

2006 elif nbunch in self: # if nbunch is a single node 

2007 bunch = iter([nbunch]) 

2008 else: # if nbunch is a sequence of nodes 

2009 

2010 def bunch_iter(nlist, adj): 

2011 try: 

2012 for n in nlist: 

2013 if n in adj: 

2014 yield n 

2015 except TypeError as err: 

2016 exc, message = err, err.args[0] 

2017 # capture error for non-sequence/iterator nbunch. 

2018 if "iter" in message: 

2019 exc = NetworkXError( 

2020 "nbunch is not a node or a sequence of nodes." 

2021 ) 

2022 # capture error for unhashable node. 

2023 if "hashable" in message: 

2024 exc = NetworkXError( 

2025 f"Node {n} in sequence nbunch is not a valid node." 

2026 ) 

2027 raise exc 

2028 

2029 bunch = bunch_iter(nbunch, self._adj) 

2030 return bunch