Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/classes/multigraph.py: 51%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

175 statements  

1"""Base class for MultiGraph.""" 

2 

3from copy import deepcopy 

4from functools import cached_property 

5 

6import networkx as nx 

7from networkx import NetworkXError, convert 

8from networkx.classes.coreviews import MultiAdjacencyView 

9from networkx.classes.graph import Graph 

10from networkx.classes.reportviews import MultiDegreeView, MultiEdgeView 

11 

12__all__ = ["MultiGraph"] 

13 

14 

15class MultiGraph(Graph): 

16 """ 

17 An undirected graph class that can store multiedges. 

18 

19 Multiedges are multiple edges between two nodes. Each edge 

20 can hold optional data or attributes. 

21 

22 A MultiGraph holds undirected edges. Self loops are allowed. 

23 

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

25 key/value attributes. By convention `None` is not used as a node. 

26 

27 Edges are represented as links between nodes with optional 

28 key/value attributes, in a MultiGraph each edge has a key to 

29 distinguish between multiple edges that have the same source and 

30 destination nodes. 

31 

32 Parameters 

33 ---------- 

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

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

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

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

38 dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, 

39 SciPy sparse array, or PyGraphviz graph. 

40 

41 multigraph_input : bool or None (default None) 

42 Note: Only used when `incoming_graph_data` is a dict. 

43 If True, `incoming_graph_data` is assumed to be a 

44 dict-of-dict-of-dict-of-dict structure keyed by 

45 node to neighbor to edge keys to edge data for multi-edges. 

46 A NetworkXError is raised if this is not the case. 

47 If False, :func:`to_networkx_graph` is used to try to determine 

48 the dict's graph data structure as either a dict-of-dict-of-dict 

49 keyed by node to neighbor to edge data, or a dict-of-iterable 

50 keyed by node to neighbors. 

51 If None, the treatment for True is tried, but if it fails, 

52 the treatment for False is tried. 

53 

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

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

56 

57 See Also 

58 -------- 

59 Graph 

60 DiGraph 

61 MultiDiGraph 

62 

63 Examples 

64 -------- 

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

66 no edges. 

67 

68 >>> G = nx.MultiGraph() 

69 

70 G can be grown in several ways. 

71 

72 **Nodes:** 

73 

74 Add one node at a time: 

75 

76 >>> G.add_node(1) 

77 

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

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

80 

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

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

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

84 >>> G.add_nodes_from(H) 

85 

86 In addition to strings and integers any hashable Python object 

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

88 or even another Graph. 

89 

90 >>> G.add_node(H) 

91 

92 **Edges:** 

93 

94 G can also be grown by adding edges. 

95 

96 Add one edge, 

97 

98 >>> key = G.add_edge(1, 2) 

99 

100 a list of edges, 

101 

102 >>> keys = G.add_edges_from([(1, 2), (1, 3)]) 

103 

104 or a collection of edges, 

105 

106 >>> keys = G.add_edges_from(H.edges) 

107 

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

109 are added automatically. If an edge already exists, an additional 

110 edge is created and stored using a key to identify the edge. 

111 By default the key is the lowest unused integer. 

112 

113 >>> keys = G.add_edges_from([(4, 5, {"route": 28}), (4, 5, {"route": 37})]) 

114 >>> G[4] 

115 AdjacencyView({3: {0: {}}, 5: {0: {}, 1: {'route': 28}, 2: {'route': 37}}}) 

116 

117 **Attributes:** 

118 

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

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

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

122 add_edge, add_node or direct manipulation of the attribute 

123 dictionaries named graph, node and edge respectively. 

124 

125 >>> G = nx.MultiGraph(day="Friday") 

126 >>> G.graph 

127 {'day': 'Friday'} 

128 

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

130 

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

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

133 >>> G.nodes[1] 

134 {'time': '5pm'} 

135 >>> G.nodes[1]["room"] = 714 

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

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

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

139 

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

141 notation, or G.edges. 

142 

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

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

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

146 >>> G[1][2][0]["weight"] = 4.7 

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

148 

149 Warning: we protect the graph data structure by making `G.edges[1, 

150 2, 0]` a read-only dict-like structure. However, you can assign to 

151 attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets 

152 to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`. 

153 

154 **Shortcuts:** 

155 

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

157 

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

159 True 

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

161 [1, 2] 

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

163 5 

164 >>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes 

165 AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}}) 

166 

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

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

169 

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

171 ... for nbr, keydict in nbrsdict.items(): 

172 ... for key, eattr in keydict.items(): 

173 ... if "weight" in eattr: 

174 ... # Do something useful with the edges 

175 ... pass 

176 

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

178 

179 >>> for u, v, keys, weight in G.edges(data="weight", keys=True): 

180 ... if weight is not None: 

181 ... # Do something useful with the edges 

182 ... pass 

183 

184 **Reporting:** 

185 

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

187 Reporting usually provides views instead of containers to reduce memory 

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

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

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

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

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

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

194 

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

196 

197 **Subclasses (Advanced):** 

198 

199 The MultiGraph class uses a dict-of-dict-of-dict-of-dict data structure. 

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

201 The next dict (adjlist_dict) represents the adjacency information 

202 and holds edge_key dicts keyed by neighbor. The edge_key dict holds 

203 each edge_attr dict keyed by edge key. The inner dict 

204 (edge_attr_dict) represents the edge data and holds edge attribute 

205 values keyed by attribute names. 

206 

207 Each of these four dicts in the dict-of-dict-of-dict-of-dict 

208 structure can be replaced by a user defined dict-like object. 

209 In general, the dict-like features should be maintained but 

210 extra features can be added. To replace one of the dicts create 

211 a new graph class by changing the class(!) variable holding the 

212 factory for that dict-like structure. The variable names are 

213 node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory, 

214 adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory 

215 and graph_attr_dict_factory. 

216 

217 node_dict_factory : function, (default: dict) 

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

219 attributes, keyed by node id. 

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

221 

222 node_attr_dict_factory: function, (default: dict) 

223 Factory function to be used to create the node attribute 

224 dict which holds attribute values keyed by attribute name. 

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

226 

227 adjlist_outer_dict_factory : function, (default: dict) 

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

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

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

231 

232 adjlist_inner_dict_factory : function, (default: dict) 

233 Factory function to be used to create the adjacency list 

234 dict which holds multiedge key dicts keyed by neighbor. 

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

236 

237 edge_key_dict_factory : function, (default: dict) 

238 Factory function to be used to create the edge key dict 

239 which holds edge data keyed by edge key. 

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

241 

242 edge_attr_dict_factory : function, (default: dict) 

243 Factory function to be used to create the edge attribute 

244 dict which holds attribute values keyed by attribute name. 

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

246 

247 graph_attr_dict_factory : function, (default: dict) 

248 Factory function to be used to create the graph attribute 

249 dict which holds attribute values keyed by attribute name. 

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

251 

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

253 methods will inherited without issue except: `to_directed/to_undirected`. 

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

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

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

257 

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

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

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

261 

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

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

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

265 

266 **Subclassing Example** 

267 

268 Create a low memory graph class that effectively disallows edge 

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

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

271 

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

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

274 ... 

275 ... def single_edge_dict(self): 

276 ... return self.all_edge_dict 

277 ... 

278 ... edge_attr_dict_factory = single_edge_dict 

279 >>> G = ThinGraph() 

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

281 >>> G[2][1] 

282 {'weight': 1} 

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

284 >>> G[2][1] is G[2][2] 

285 True 

286 """ 

287 

288 # node_dict_factory = dict # already assigned in Graph 

289 # adjlist_outer_dict_factory = dict 

290 # adjlist_inner_dict_factory = dict 

291 edge_key_dict_factory = dict 

292 # edge_attr_dict_factory = dict 

293 

294 def to_directed_class(self): 

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

296 

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

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

299 """ 

300 return nx.MultiDiGraph 

301 

302 def to_undirected_class(self): 

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

304 

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

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

307 """ 

308 return MultiGraph 

309 

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

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

312 

313 Parameters 

314 ---------- 

315 incoming_graph_data : input graph 

316 Data to initialize graph. If incoming_graph_data=None (default) 

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

318 NetworkX graph object. If the corresponding optional Python 

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

320 SciPy sparse array, or a PyGraphviz graph. 

321 

322 multigraph_input : bool or None (default None) 

323 Note: Only used when `incoming_graph_data` is a dict. 

324 If True, `incoming_graph_data` is assumed to be a 

325 dict-of-dict-of-dict-of-dict structure keyed by 

326 node to neighbor to edge keys to edge data for multi-edges. 

327 A NetworkXError is raised if this is not the case. 

328 If False, :func:`to_networkx_graph` is used to try to determine 

329 the dict's graph data structure as either a dict-of-dict-of-dict 

330 keyed by node to neighbor to edge data, or a dict-of-iterable 

331 keyed by node to neighbors. 

332 If None, the treatment for True is tried, but if it fails, 

333 the treatment for False is tried. 

334 

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

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

337 

338 See Also 

339 -------- 

340 convert 

341 

342 Examples 

343 -------- 

344 >>> G = nx.MultiGraph() 

345 >>> G = nx.MultiGraph(name="my graph") 

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

347 >>> G = nx.MultiGraph(e) 

348 

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

350 

351 >>> G = nx.MultiGraph(e, day="Friday") 

352 >>> G.graph 

353 {'day': 'Friday'} 

354 

355 """ 

356 # multigraph_input can be None/True/False. So check "is not False" 

357 if isinstance(incoming_graph_data, dict) and multigraph_input is not False: 

358 Graph.__init__(self) 

359 try: 

360 convert.from_dict_of_dicts( 

361 incoming_graph_data, create_using=self, multigraph_input=True 

362 ) 

363 self.graph.update(attr) 

364 except Exception as err: 

365 if multigraph_input is True: 

366 raise nx.NetworkXError( 

367 f"converting multigraph_input raised:\n{type(err)}: {err}" 

368 ) 

369 Graph.__init__(self, incoming_graph_data, **attr) 

370 else: 

371 Graph.__init__(self, incoming_graph_data, **attr) 

372 

373 @cached_property 

374 def adj(self): 

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

376 

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

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

379 to the edgekey-data-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets 

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

381 

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

383 `for nbr, edgesdict in G.adj[n].items():`. 

384 

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

386 

387 Examples 

388 -------- 

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

390 >>> G = nx.MultiGraph(e) 

391 >>> G.edges[1, 2, 0]["weight"] = 3 

392 >>> result = set() 

393 >>> for edgekey, data in G[1][2].items(): 

394 ... result.add(data.get("weight", 1)) 

395 >>> result 

396 {1, 3} 

397 

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

399 """ 

400 return MultiAdjacencyView(self._adj) 

401 

402 def new_edge_key(self, u, v): 

403 """Returns an unused key for edges between nodes `u` and `v`. 

404 

405 The nodes `u` and `v` do not need to be already in the graph. 

406 

407 Notes 

408 ----- 

409 In the standard MultiGraph class the new key is the number of existing 

410 edges between `u` and `v` (increased if necessary to ensure unused). 

411 The first edge will have key 0, then 1, etc. If an edge is removed 

412 further new_edge_keys may not be in this order. 

413 

414 Parameters 

415 ---------- 

416 u, v : nodes 

417 

418 Returns 

419 ------- 

420 key : int 

421 """ 

422 try: 

423 keydict = self._adj[u][v] 

424 except KeyError: 

425 return 0 

426 key = len(keydict) 

427 while key in keydict: 

428 key += 1 

429 return key 

430 

431 def add_edge(self, u_for_edge, v_for_edge, key=None, **attr): 

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

433 

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

435 not already in the graph. 

436 

437 Edge attributes can be specified with keywords or by directly 

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

439 

440 Parameters 

441 ---------- 

442 u_for_edge, v_for_edge : nodes 

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

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

445 key : hashable identifier, optional (default=lowest unused integer) 

446 Used to distinguish multiedges between a pair of nodes. 

447 attr : keyword arguments, optional 

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

449 keyword arguments. 

450 

451 Returns 

452 ------- 

453 The edge key assigned to the edge. 

454 

455 See Also 

456 -------- 

457 add_edges_from : add a collection of edges 

458 

459 Notes 

460 ----- 

461 To replace/update edge data, use the optional key argument 

462 to identify a unique edge. Otherwise a new edge will be created. 

463 

464 NetworkX algorithms designed for weighted graphs cannot use 

465 multigraphs directly because it is not clear how to handle 

466 multiedge weights. Convert to Graph using edge attribute 

467 'weight' to enable weighted graph algorithms. 

468 

469 Default keys are generated using the method `new_edge_key()`. 

470 This method can be overridden by subclassing the base class and 

471 providing a custom `new_edge_key()` method. 

472 

473 Examples 

474 -------- 

475 The following each add an additional edge e=(1, 2) to graph G: 

476 

477 >>> G = nx.MultiGraph() 

478 >>> e = (1, 2) 

479 >>> ekey = G.add_edge(1, 2) # explicit two-node form 

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

481 1 

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

483 [2] 

484 

485 Associate data to edges using keywords: 

486 

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

488 >>> ekey = G.add_edge(1, 2, key=0, weight=4) # update data for key=0 

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

490 

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

492 

493 >>> ekey = G.add_edge(1, 2) 

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

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

496 """ 

497 u, v = u_for_edge, v_for_edge 

498 # add nodes 

499 if u not in self._adj: 

500 if u is None: 

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

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

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

504 if v not in self._adj: 

505 if v is None: 

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

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

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

509 if key is None: 

510 key = self.new_edge_key(u, v) 

511 if v in self._adj[u]: 

512 keydict = self._adj[u][v] 

513 datadict = keydict.get(key, self.edge_attr_dict_factory()) 

514 datadict.update(attr) 

515 keydict[key] = datadict 

516 else: 

517 # selfloops work this way without special treatment 

518 datadict = self.edge_attr_dict_factory() 

519 datadict.update(attr) 

520 keydict = self.edge_key_dict_factory() 

521 keydict[key] = datadict 

522 self._adj[u][v] = keydict 

523 self._adj[v][u] = keydict 

524 nx._clear_cache(self) 

525 return key 

526 

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

528 """Add all the edges in ebunch_to_add. 

529 

530 Parameters 

531 ---------- 

532 ebunch_to_add : container of edges 

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

534 graph. The edges can be: 

535 

536 - 2-tuples (u, v) or 

537 - 3-tuples (u, v, d) for an edge data dict d, or 

538 - 3-tuples (u, v, k) for not iterable key k, or 

539 - 4-tuples (u, v, k, d) for an edge with data and key k 

540 

541 attr : keyword arguments, optional 

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

543 keyword arguments. 

544 

545 Returns 

546 ------- 

547 A list of edge keys assigned to the edges in `ebunch`. 

548 

549 See Also 

550 -------- 

551 add_edge : add a single edge 

552 add_weighted_edges_from : convenient way to add weighted edges 

553 

554 Notes 

555 ----- 

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

557 will be updated when each duplicate edge is added. 

558 

559 Edge attributes specified in an ebunch take precedence over 

560 attributes specified via keyword arguments. 

561 

562 Default keys are generated using the method ``new_edge_key()``. 

563 This method can be overridden by subclassing the base class and 

564 providing a custom ``new_edge_key()`` method. 

565 

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

567 a `RuntimeError` can be raised with message: 

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

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

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

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

572 object to `G.add_edges_from`. 

573 

574 Examples 

575 -------- 

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

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

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

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

580 

581 Associate data to edges 

582 

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

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

585 

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

587 

588 >>> G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)]) 

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

590 >>> # wrong way - will raise RuntimeError 

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

592 >>> # right way - note that there will be no self-edge for node 5 

593 >>> assigned_keys = G.add_edges_from(list((5, n) for n in G.nodes)) 

594 """ 

595 keylist = [] 

596 for e in ebunch_to_add: 

597 ne = len(e) 

598 if ne == 4: 

599 u, v, key, dd = e 

600 elif ne == 3: 

601 u, v, dd = e 

602 key = None 

603 elif ne == 2: 

604 u, v = e 

605 dd = {} 

606 key = None 

607 else: 

608 msg = f"Edge tuple {e} must be a 2-tuple, 3-tuple or 4-tuple." 

609 raise NetworkXError(msg) 

610 ddd = {} 

611 ddd.update(attr) 

612 try: 

613 ddd.update(dd) 

614 except (TypeError, ValueError): 

615 if ne != 3: 

616 raise 

617 key = dd # ne == 3 with 3rd value not dict, must be a key 

618 key = self.add_edge(u, v, key) 

619 self[u][v][key].update(ddd) 

620 keylist.append(key) 

621 nx._clear_cache(self) 

622 return keylist 

623 

624 def remove_edge(self, u, v, key=None): 

625 """Remove an edge between u and v. 

626 

627 Parameters 

628 ---------- 

629 u, v : nodes 

630 Remove an edge between nodes u and v. 

631 key : hashable identifier, optional (default=None) 

632 Used to distinguish multiple edges between a pair of nodes. 

633 If None, remove a single edge between u and v. If there are 

634 multiple edges, removes the last edge added in terms of 

635 insertion order. 

636 

637 Raises 

638 ------ 

639 NetworkXError 

640 If there is not an edge between u and v, or 

641 if there is no edge with the specified key. 

642 

643 See Also 

644 -------- 

645 remove_edges_from : remove a collection of edges 

646 

647 Examples 

648 -------- 

649 >>> G = nx.MultiGraph() 

650 >>> nx.add_path(G, [0, 1, 2, 3]) 

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

652 >>> e = (1, 2) 

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

654 

655 For multiple edges 

656 

657 >>> G = nx.MultiGraph() # or MultiDiGraph, etc 

658 >>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned 

659 [0, 1, 2] 

660 

661 When ``key=None`` (the default), edges are removed in the opposite 

662 order that they were added: 

663 

664 >>> G.remove_edge(1, 2) 

665 >>> G.edges(keys=True) 

666 MultiEdgeView([(1, 2, 0), (1, 2, 1)]) 

667 >>> G.remove_edge(2, 1) # edges are not directed 

668 >>> G.edges(keys=True) 

669 MultiEdgeView([(1, 2, 0)]) 

670 

671 For edges with keys 

672 

673 >>> G = nx.MultiGraph() 

674 >>> G.add_edge(1, 2, key="first") 

675 'first' 

676 >>> G.add_edge(1, 2, key="second") 

677 'second' 

678 >>> G.remove_edge(1, 2, key="first") 

679 >>> G.edges(keys=True) 

680 MultiEdgeView([(1, 2, 'second')]) 

681 

682 """ 

683 try: 

684 d = self._adj[u][v] 

685 except KeyError as err: 

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

687 # remove the edge with specified data 

688 if key is None: 

689 d.popitem() 

690 else: 

691 try: 

692 del d[key] 

693 except KeyError as err: 

694 msg = f"The edge {u}-{v} with key {key} is not in the graph." 

695 raise NetworkXError(msg) from err 

696 if len(d) == 0: 

697 # remove the key entries if last edge 

698 del self._adj[u][v] 

699 if u != v: # check for selfloop 

700 del self._adj[v][u] 

701 nx._clear_cache(self) 

702 

703 def remove_edges_from(self, ebunch): 

704 """Remove all edges specified in ebunch. 

705 

706 Parameters 

707 ---------- 

708 ebunch: list or container of edge tuples 

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

710 from the graph. The edges can be: 

711 

712 - 2-tuples (u, v) A single edge between u and v is removed. 

713 - 3-tuples (u, v, key) The edge identified by key is removed. 

714 - 4-tuples (u, v, key, data) where data is ignored. 

715 

716 See Also 

717 -------- 

718 remove_edge : remove a single edge 

719 

720 Notes 

721 ----- 

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

723 

724 Examples 

725 -------- 

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

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

728 >>> G.remove_edges_from(ebunch) 

729 

730 Removing multiple copies of edges 

731 

732 >>> G = nx.MultiGraph() 

733 >>> keys = G.add_edges_from([(1, 2), (1, 2), (1, 2)]) 

734 >>> G.remove_edges_from([(1, 2), (2, 1)]) # edges aren't directed 

735 >>> list(G.edges()) 

736 [(1, 2)] 

737 >>> G.remove_edges_from([(1, 2), (1, 2)]) # silently ignore extra copy 

738 >>> list(G.edges) # now empty graph 

739 [] 

740 

741 When the edge is a 2-tuple ``(u, v)`` but there are multiple edges between 

742 u and v in the graph, the most recent edge (in terms of insertion 

743 order) is removed. 

744 

745 >>> G = nx.MultiGraph() 

746 >>> for key in ("x", "y", "a"): 

747 ... k = G.add_edge(0, 1, key=key) 

748 >>> G.edges(keys=True) 

749 MultiEdgeView([(0, 1, 'x'), (0, 1, 'y'), (0, 1, 'a')]) 

750 >>> G.remove_edges_from([(0, 1)]) 

751 >>> G.edges(keys=True) 

752 MultiEdgeView([(0, 1, 'x'), (0, 1, 'y')]) 

753 

754 """ 

755 for e in ebunch: 

756 try: 

757 self.remove_edge(*e[:3]) 

758 except NetworkXError: 

759 pass 

760 nx._clear_cache(self) 

761 

762 def has_edge(self, u, v, key=None): 

763 """Returns True if the graph has an edge between nodes u and v. 

764 

765 This is the same as `v in G[u] or key in G[u][v]` 

766 without KeyError exceptions. 

767 

768 Parameters 

769 ---------- 

770 u, v : nodes 

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

772 

773 key : hashable identifier, optional (default=None) 

774 If specified return True only if the edge with 

775 key is found. 

776 

777 Returns 

778 ------- 

779 edge_ind : bool 

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

781 

782 Examples 

783 -------- 

784 Can be called either using two nodes u, v, an edge tuple (u, v), 

785 or an edge tuple (u, v, key). 

786 

787 >>> G = nx.MultiGraph() # or MultiDiGraph 

788 >>> nx.add_path(G, [0, 1, 2, 3]) 

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

790 True 

791 >>> e = (0, 1) 

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

793 True 

794 >>> G.add_edge(0, 1, key="a") 

795 'a' 

796 >>> G.has_edge(0, 1, key="a") # specify key 

797 True 

798 >>> G.has_edge(1, 0, key="a") # edges aren't directed 

799 True 

800 >>> e = (0, 1, "a") 

801 >>> G.has_edge(*e) # e is a 3-tuple (u, v, 'a') 

802 True 

803 

804 The following syntax are equivalent: 

805 

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

807 True 

808 >>> 1 in G[0] # though this gives :exc:`KeyError` if 0 not in G 

809 True 

810 >>> 0 in G[1] # other order; also gives :exc:`KeyError` if 0 not in G 

811 True 

812 

813 """ 

814 try: 

815 if key is None: 

816 return v in self._adj[u] 

817 else: 

818 return key in self._adj[u][v] 

819 except KeyError: 

820 return False 

821 

822 @cached_property 

823 def edges(self): 

824 """Returns an iterator over the edges. 

825 

826 edges(self, nbunch=None, data=False, keys=False, default=None) 

827 

828 The MultiEdgeView provides set-like operations on the edge-tuples 

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

830 an EdgeDataView object which allows control of access to edge 

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

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

833 attribute for the edge from ``u`` to ``v`` with key ``k`` while 

834 ``for (u, v, k, c) in G.edges(data='color', keys=True, default="red"):`` 

835 iterates through all the edges yielding the color attribute with 

836 default `'red'` if no color attribute exists. 

837 

838 Edges are returned as tuples with optional data and keys 

839 in the order (node, neighbor, key, data). If ``keys=True`` is not 

840 provided, the tuples will just be (node, neighbor, data), but 

841 multiple tuples with the same node and neighbor will be generated 

842 when multiple edges exist between two nodes. 

843 

844 Parameters 

845 ---------- 

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

847 The view will only report edges from these nodes. 

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

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

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

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

852 keys : bool, optional (default=False) 

853 If True, return edge keys with each edge, creating (u, v, k) 

854 tuples or (u, v, k, d) tuples if data is also requested. 

855 default : value, optional (default=None) 

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

857 Only relevant if data is not True or False. 

858 

859 Returns 

860 ------- 

861 edges : MultiEdgeView 

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

863 (u, v, k) or (u, v, k, d) tuples of edges, but can also be 

864 used for attribute lookup as ``edges[u, v, k]['foo']``. 

865 

866 Notes 

867 ----- 

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

869 For directed graphs this returns the out-edges. 

870 

871 Examples 

872 -------- 

873 >>> G = nx.MultiGraph() 

874 >>> nx.add_path(G, [0, 1, 2]) 

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

876 >>> key2 = G.add_edge(2, 1, weight=2) # multi-edge 

877 >>> [e for e in G.edges()] 

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

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

880 MultiEdgeDataView([(0, 1, {}), (1, 2, {}), (1, 2, {'weight': 2}), (2, 3, {'weight': 5})]) 

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

882 MultiEdgeDataView([(0, 1, 1), (1, 2, 1), (1, 2, 2), (2, 3, 5)]) 

883 >>> G.edges(keys=True) # default keys are integers 

884 MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)]) 

885 >>> G.edges.data(keys=True) 

886 MultiEdgeDataView([(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {'weight': 2}), (2, 3, 0, {'weight': 5})]) 

887 >>> G.edges.data("weight", default=1, keys=True) 

888 MultiEdgeDataView([(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 2), (2, 3, 0, 5)]) 

889 >>> G.edges([0, 3]) # Note ordering of tuples from listed sources 

890 MultiEdgeDataView([(0, 1), (3, 2)]) 

891 >>> G.edges([0, 3, 2, 1]) # Note ordering of tuples 

892 MultiEdgeDataView([(0, 1), (3, 2), (2, 1), (2, 1)]) 

893 >>> G.edges(0) 

894 MultiEdgeDataView([(0, 1)]) 

895 """ 

896 return MultiEdgeView(self) 

897 

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

899 """Returns the attribute dictionary associated with edge (u, v, 

900 key). 

901 

902 If a key is not provided, returns a dictionary mapping edge keys 

903 to attribute dictionaries for each edge between u and v. 

904 

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

906 instead of an exception is the edge doesn't exist. 

907 

908 Parameters 

909 ---------- 

910 u, v : nodes 

911 

912 default : any Python object (default=None) 

913 Value to return if the specific edge (u, v, key) is not 

914 found, OR if there are no edges between u and v and no key 

915 is specified. 

916 

917 key : hashable identifier, optional (default=None) 

918 Return data only for the edge with specified key, as an 

919 attribute dictionary (rather than a dictionary mapping keys 

920 to attribute dictionaries). 

921 

922 Returns 

923 ------- 

924 edge_dict : dictionary 

925 The edge attribute dictionary, OR a dictionary mapping edge 

926 keys to attribute dictionaries for each of those edges if no 

927 specific key is provided (even if there's only one edge 

928 between u and v). 

929 

930 Examples 

931 -------- 

932 >>> G = nx.MultiGraph() # or MultiDiGraph 

933 >>> key = G.add_edge(0, 1, key="a", weight=7) 

934 >>> G[0][1]["a"] # key='a' 

935 {'weight': 7} 

936 >>> G.edges[0, 1, "a"] # key='a' 

937 {'weight': 7} 

938 

939 Warning: we protect the graph data structure by making 

940 `G.edges` and `G[1][2]` read-only dict-like structures. 

941 However, you can assign values to attributes in e.g. 

942 `G.edges[1, 2, 'a']` or `G[1][2]['a']` using an additional 

943 bracket as shown next. You need to specify all edge info 

944 to assign to the edge data associated with an edge. 

945 

946 >>> G[0][1]["a"]["weight"] = 10 

947 >>> G.edges[0, 1, "a"]["weight"] = 10 

948 >>> G[0][1]["a"]["weight"] 

949 10 

950 >>> G.edges[1, 0, "a"]["weight"] 

951 10 

952 

953 >>> G = nx.MultiGraph() # or MultiDiGraph 

954 >>> nx.add_path(G, [0, 1, 2, 3]) 

955 >>> G.edges[0, 1, 0]["weight"] = 5 

956 >>> G.get_edge_data(0, 1) 

957 {0: {'weight': 5}} 

958 >>> e = (0, 1) 

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

960 {0: {'weight': 5}} 

961 >>> G.get_edge_data(3, 0) # edge not in graph, returns None 

962 >>> G.get_edge_data(3, 0, default=0) # edge not in graph, return default 

963 0 

964 >>> G.get_edge_data(1, 0, 0) # specific key gives back 

965 {'weight': 5} 

966 """ 

967 try: 

968 if key is None: 

969 return self._adj[u][v] 

970 else: 

971 return self._adj[u][v][key] 

972 except KeyError: 

973 return default 

974 

975 @cached_property 

976 def degree(self): 

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

978 

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

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

981 edges incident to that node. 

982 

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

984 lookup for the degree for a single node. 

985 

986 Parameters 

987 ---------- 

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

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

990 

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

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

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

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

995 

996 Returns 

997 ------- 

998 MultiDegreeView or int 

999 If multiple nodes are requested (the default), returns a `MultiDegreeView` 

1000 mapping nodes to their degree. 

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

1002 

1003 Examples 

1004 -------- 

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

1006 >>> nx.add_path(G, [0, 1, 2, 3]) 

1007 >>> G.degree(0) # node 0 with degree 1 

1008 1 

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

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

1011 

1012 """ 

1013 return MultiDegreeView(self) 

1014 

1015 def is_multigraph(self): 

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

1017 return True 

1018 

1019 def is_directed(self): 

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

1021 return False 

1022 

1023 def copy(self, as_view=False): 

1024 """Returns a copy of the graph. 

1025 

1026 The copy method by default returns an independent shallow copy 

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

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

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

1030 

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

1032 

1033 Notes 

1034 ----- 

1035 All copies reproduce the graph structure, but data attributes 

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

1037 of a graph that people might want. 

1038 

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

1040 all data attributes and any objects they might contain. 

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

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

1043 

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

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

1046 references to those in the original graph. This saves 

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

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

1049 NetworkX does not provide this level of shallow copy. 

1050 

1051 Independent Shallow -- This copy creates new independent attribute 

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

1053 attributes that are containers are shared between the new graph 

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

1055 You can obtain this style copy using: 

1056 

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

1058 >>> H = G.copy() 

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

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

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

1062 

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

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

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

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

1067 

1068 >>> H = G.__class__() 

1069 >>> H.add_nodes_from(G) 

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

1071 

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

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

1074 structure without requiring any memory for copying the information. 

1075 

1076 See the Python copy module for more information on shallow 

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

1078 

1079 Parameters 

1080 ---------- 

1081 as_view : bool, optional (default=False) 

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

1083 of the original graph without actually copying any data. 

1084 

1085 Returns 

1086 ------- 

1087 G : Graph 

1088 A copy of the graph. 

1089 

1090 See Also 

1091 -------- 

1092 to_directed: return a directed copy of the graph. 

1093 

1094 Examples 

1095 -------- 

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

1097 >>> H = G.copy() 

1098 

1099 """ 

1100 if as_view is True: 

1101 return nx.graphviews.generic_graph_view(self) 

1102 G = self.__class__() 

1103 G.graph.update(self.graph) 

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

1105 G.add_edges_from( 

1106 (u, v, key, datadict.copy()) 

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

1108 for v, keydict in nbrs.items() 

1109 for key, datadict in keydict.items() 

1110 ) 

1111 return G 

1112 

1113 def to_directed(self, as_view=False): 

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

1115 

1116 Returns 

1117 ------- 

1118 G : MultiDiGraph 

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

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

1121 (u, v, k, data) and (v, u, k, data). 

1122 

1123 Notes 

1124 ----- 

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

1126 graph attributes which attempts to completely copy 

1127 all of the data and references. 

1128 

1129 This is in contrast to the similar D=MultiDiGraph(G) which 

1130 returns a shallow copy of the data. 

1131 

1132 See the Python copy module for more information on shallow 

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

1134 

1135 Warning: If you have subclassed MultiGraph to use dict-like objects 

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

1137 MultiDiGraph created by this method. 

1138 

1139 Examples 

1140 -------- 

1141 >>> G = nx.MultiGraph() 

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

1143 0 

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

1145 1 

1146 >>> H = G.to_directed() 

1147 >>> list(H.edges) 

1148 [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1)] 

1149 

1150 If already directed, return a (deep) copy 

1151 

1152 >>> G = nx.MultiDiGraph() 

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

1154 0 

1155 >>> H = G.to_directed() 

1156 >>> list(H.edges) 

1157 [(0, 1, 0)] 

1158 """ 

1159 graph_class = self.to_directed_class() 

1160 if as_view is True: 

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

1162 # deepcopy when not a view 

1163 G = graph_class() 

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

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

1166 G.add_edges_from( 

1167 (u, v, key, deepcopy(datadict)) 

1168 for u, nbrs in self.adj.items() 

1169 for v, keydict in nbrs.items() 

1170 for key, datadict in keydict.items() 

1171 ) 

1172 return G 

1173 

1174 def to_undirected(self, as_view=False): 

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

1176 

1177 Returns 

1178 ------- 

1179 G : Graph/MultiGraph 

1180 A deepcopy of the graph. 

1181 

1182 See Also 

1183 -------- 

1184 copy, add_edge, add_edges_from 

1185 

1186 Notes 

1187 ----- 

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

1189 graph attributes which attempts to completely copy 

1190 all of the data and references. 

1191 

1192 This is in contrast to the similar `G = nx.MultiGraph(D)` 

1193 which returns a shallow copy of the data. 

1194 

1195 See the Python copy module for more information on shallow 

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

1197 

1198 Warning: If you have subclassed MultiGraph to use dict-like 

1199 objects in the data structure, those changes do not transfer 

1200 to the MultiGraph created by this method. 

1201 

1202 Examples 

1203 -------- 

1204 >>> G = nx.MultiGraph([(0, 1), (0, 1), (1, 2)]) 

1205 >>> H = G.to_directed() 

1206 >>> list(H.edges) 

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

1208 >>> G2 = H.to_undirected() 

1209 >>> list(G2.edges) 

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

1211 """ 

1212 graph_class = self.to_undirected_class() 

1213 if as_view is True: 

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

1215 # deepcopy when not a view 

1216 G = graph_class() 

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

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

1219 G.add_edges_from( 

1220 (u, v, key, deepcopy(datadict)) 

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

1222 for v, keydict in nbrs.items() 

1223 for key, datadict in keydict.items() 

1224 ) 

1225 return G 

1226 

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

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

1229 

1230 Parameters 

1231 ---------- 

1232 u, v : nodes, optional (Default=all edges) 

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

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

1235 

1236 Returns 

1237 ------- 

1238 nedges : int 

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

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

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

1242 from `u` to `v`. 

1243 

1244 See Also 

1245 -------- 

1246 size 

1247 

1248 Examples 

1249 -------- 

1250 For undirected multigraphs, this method counts the total number 

1251 of edges in the graph:: 

1252 

1253 >>> G = nx.MultiGraph() 

1254 >>> G.add_edges_from([(0, 1), (0, 1), (1, 2)]) 

1255 [0, 1, 0] 

1256 >>> G.number_of_edges() 

1257 3 

1258 

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

1260 joining the two nodes:: 

1261 

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

1263 2 

1264 

1265 For directed multigraphs, this method can count the total number 

1266 of directed edges from `u` to `v`:: 

1267 

1268 >>> G = nx.MultiDiGraph() 

1269 >>> G.add_edges_from([(0, 1), (0, 1), (1, 0)]) 

1270 [0, 1, 0] 

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

1272 2 

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

1274 1 

1275 

1276 """ 

1277 if u is None: 

1278 return self.size() 

1279 try: 

1280 edgedata = self._adj[u][v] 

1281 except KeyError: 

1282 return 0 # no such edge 

1283 return len(edgedata)