1"""
2View Classes provide node, edge and degree "views" of a graph.
3
4As with dicts, the graph should not be updated while
5iterating through the view. Views can be iterated multiple times.
6
7Views are quick-to-create, read-only iterable containers,
8live (they reflect changes to the graph), and provide
9Pythonic access patterns for all base graph classes:
10
11- set-like membership and set operations for nodes and edges (``n in G.nodes``,
12 ``G.nodes & H.nodes``, ``(u, v) in G.edges``),
13- iteration (``for n in G.nodes``, ``for u, v in G.edges``),
14- mapping-style lookups (``G.nodes[n]`` returns the node attribute dict;
15 ``G.edges[u, v]`` returns the edge attribute dict),
16- data-filtered iteration via ``.data(...)`` and conversion to concrete
17 containers via ``list(...)`` or ``dict(...)``.
18
19The resulting attribute dict is writable as ``G.edges[3, 4]['color'] = 'red'``
20Degree views allow lookup of degree values for single nodes.
21Weighted degree is supported with the ``weight`` argument.
22
23NodeView
24========
25
26``V = G.nodes`` (or ``V = G.nodes()``) allows ``len(V)``, ``n in V``, set
27operations e.g. ``G.nodes & H.nodes``, and ``dd = G.nodes[n]``, where
28``dd`` is the node data dict. Iteration is over the nodes by default.
29
30NodeDataView
31============
32
33To iterate over ``(node, data)`` pairs, use arguments to ``G.nodes()``
34to create a DataView e.g. ``DV = G.nodes(data='color', default='red')``.
35The DataView iterates as ``for n, color in DV`` and allows
36``(n, 'red') in DV``. Using ``DV = G.nodes(data=True)``, the DataViews
37use the full datadict in writeable form also allowing contain testing as
38``(n, {'color': 'red'}) in VD``. DataViews allow set operations when
39data attributes are hashable.
40
41DegreeView
42==========
43
44``V = G.degree`` allows iteration over ``(node, degree)`` pairs as well
45as lookup: ``deg = V[n]``. There are many flavors of DegreeView
46for In/Out/Directed/Multi. For Directed Graphs, ``G.degree``
47counts both in and out going edges. ``G.out_degree`` and
48``G.in_degree`` count only specific directions.
49Weighted degree using edge data attributes is provide via
50``V = G.degree(weight='attr_name')`` where any string with the
51attribute name can be used. ``weight=None`` is the default.
52No set operations are implemented for degrees, use NodeView.
53
54The argument ``nbunch`` restricts iteration to nodes in nbunch.
55The DegreeView can still lookup any node even if nbunch is specified.
56
57EdgeView
58========
59
60``V = G.edges`` or ``V = G.edges()`` allows iteration over edges as well as
61``e in V``, set operations and edge data lookup ``dd = G.edges[2, 3]``.
62Iteration is over 2-tuples ``(u, v)`` for Graph/DiGraph. For multigraphs
63edges 3-tuples ``(u, v, key)`` are the default but 2-tuples can be obtained
64via ``V = G.edges(keys=False)``.
65
66Set operations for directed graphs treat the edges as a set of 2-tuples.
67For undirected graphs, 2-tuples are not a unique representation of edges.
68So long as the set being compared to contains unique representations
69of its edges, the set operations will act as expected. If the other
70set contains both ``(0, 1)`` and ``(1, 0)`` however, the result of set
71operations may contain both representations of the same edge.
72
73EdgeDataView
74============
75
76Edge data can be reported using an EdgeDataView typically created
77by calling an EdgeView: ``DV = G.edges(data='weight', default=1)``.
78The EdgeDataView allows iteration over edge tuples, membership checking
79but no set operations.
80
81Iteration depends on ``data`` and ``default`` and for multigraph ``keys``
82If ``data is False`` (the default) then iterate over 2-tuples ``(u, v)``.
83If ``data is True`` iterate over 3-tuples ``(u, v, datadict)``.
84Otherwise iterate over ``(u, v, datadict.get(data, default))``.
85For Multigraphs, if ``keys is True``, replace ``u, v`` with `u, v, key`
86to create 3-tuples and 4-tuples.
87
88The argument ``nbunch`` restricts edges to those incident to nodes in nbunch.
89
90Common usage patterns
91=====================
92
93NodeView / NodeDataView
94-----------------------
95
96- Use ``G.nodes`` when you need membership tests, set-like operations, or to
97 look up a node's attribute dict with ``G.nodes[n]``.
98- Use ``G.nodes(data=...)`` or ``G.nodes.data(attr_name, default=...)`` to
99 iterate node/data pairs or to extract a single attribute for all nodes.
100- Use ``list(G)`` or ``for node in G:`` to iterate over nodes.
101
102Example:
103
104.. code-block:: python
105
106 >>> G = nx.path_graph(3)
107 >>> list(G.nodes)
108 [0, 1, 2]
109 >>> G.add_node(3, color="red")
110 >>> list(G.nodes.data("color", default=None))
111 [(0, None), (1, None), (2, None), (3, 'red')]
112
113EdgeView / EdgeDataView
114-----------------------
115
116- Use ``G.edges`` to iterate or test membership for edges.
117- Call ``G.edges(data=...)`` or ``G.edges(nbunch=..., data=..., keys=...)`` to
118 iterate edges with data, restrict to edges incident to a set of nodes, or
119 include multigraph keys.
120
121.. note::
122 Iteration of ``G.edges()`` yields node pairs as 2-tuples even for multigraphs.
123 Iteration of `G.edges` yields 3-tuples for multigraphs and 2-tuples otherwise.
124 You can also use ``G.edges(keys=True, data=True)`` to receive 4-tuples
125 ``(u, v, key, data)`` for multigraphs.
126
127Example:
128
129.. code-block:: python
130
131 >>> G = nx.Graph()
132 >>> G.add_edge(0, 1, weight=3)
133 >>> list(G.edges(data="weight", default=1))
134 [(0, 1, 3)]
135
136DegreeView
137----------
138
139- Use ``G.degree`` to iterate ``(node, degree)`` pairs or query a single node
140 with ``G.degree[n]``.
141- The function interface ``G.degree(nbunch=..., weight=...)`` allows:
142 * ``weight="attr"`` to compute weighted degree using the named edge attribute, and
143 * ``nbunch`` to restrict iteration to a subset of nodes while still allowing direct lookups.
144
145Example:
146
147.. code-block:: python
148
149 >>> G = nx.cycle_graph(4)
150 >>> dict(G.degree())
151 {0: 2, 1: 2, 2: 2, 3: 2}
152 >>> G.degree[0]
153 2
154 >>> # Add an edge with a weight attribute and compute weighted degrees:
155 >>> G.add_edge(0, 1, weight=5)
156 >>> dict(G.degree(weight="weight"))
157 {0: 6, 1: 6, 2: 2, 3: 2}
158
159Degree Computation
160==================
161
162NetworkX does not store a persistent degree value for each node. Instead, the
163degree is computed when requested by examining the neighbor dictionary for a
164node and counting or summing edge attributes as needed. For multigraphs the key
165dict for each neighbor is scanned; for weighted degree the requested edge
166attribute values are summed.
167
168Because computing degree accesses the adjacency structures, some applications
169cache degree values in a separate dictionary to avoid repeated recomputation:
170
171.. code-block:: python
172
173 >>> G_degree = dict(G.degree) # make a cached snapshot of degrees
174
175If degrees are cached, update the cached dictionary when edges are modified.
176
177Performance & pitfalls
178======================
179
180- Views are **read-only** wrappers — they do not copy graph data. If you need a stable snapshot
181 (for example when you will modify the graph while iterating), materialize the view using
182 ``list(view)`` or ``dict(view)``.
183- Avoid modifying the graph while iterating over a view (same rule as iterating a dict).
184- DataViews that return full attribute dicts expose writable dicts (modifying those dicts
185 modifies the underlying graph attributes). Use this intentionally.
186- Edge set operations on undirected graphs use 2-tuple representations; be careful when
187 comparing sets that may contain both ``(u, v)`` and ``(v, u)``.
188- DegreeView with ``weight`` performs a sum over edge attributes and can be more expensive
189 than unweighted degree calculations.
190- When ``G`` will not change and you need many degree lookups, store
191 the precomputed degrees using ``degrees = dict(G.degree)``.
192"""
193
194from abc import ABC
195from collections.abc import Mapping, Set
196
197import networkx as nx
198
199__all__ = [
200 "NodeView",
201 "NodeDataView",
202 "EdgeView",
203 "OutEdgeView",
204 "InEdgeView",
205 "EdgeDataView",
206 "OutEdgeDataView",
207 "InEdgeDataView",
208 "MultiEdgeView",
209 "OutMultiEdgeView",
210 "InMultiEdgeView",
211 "MultiEdgeDataView",
212 "OutMultiEdgeDataView",
213 "InMultiEdgeDataView",
214 "DegreeView",
215 "DiDegreeView",
216 "InDegreeView",
217 "OutDegreeView",
218 "MultiDegreeView",
219 "DiMultiDegreeView",
220 "InMultiDegreeView",
221 "OutMultiDegreeView",
222]
223
224
225# NodeViews
226class NodeView(Mapping, Set):
227 """A NodeView class to act as G.nodes for a NetworkX Graph
228
229 Set operations act on the nodes without considering data.
230 Iteration is over nodes. Node data can be looked up like a dict.
231 Use NodeDataView to iterate over node data or to specify a data
232 attribute for lookup. NodeDataView is created by calling the NodeView.
233
234 Parameters
235 ----------
236 graph : NetworkX graph-like class
237
238 Examples
239 --------
240 >>> G = nx.path_graph(3)
241 >>> NV = G.nodes()
242 >>> 2 in NV
243 True
244 >>> for n in NV:
245 ... print(n)
246 0
247 1
248 2
249 >>> assert NV & {1, 2, 3} == {1, 2}
250
251 >>> G.add_node(2, color="blue")
252 >>> NV[2]
253 {'color': 'blue'}
254 >>> G.add_node(8, color="red")
255 >>> NDV = G.nodes(data=True)
256 >>> (2, NV[2]) in NDV
257 True
258 >>> for n, dd in NDV:
259 ... print((n, dd.get("color", "aqua")))
260 (0, 'aqua')
261 (1, 'aqua')
262 (2, 'blue')
263 (8, 'red')
264 >>> NDV[2] == NV[2]
265 True
266
267 >>> NVdata = G.nodes(data="color", default="aqua")
268 >>> (2, NVdata[2]) in NVdata
269 True
270 >>> for n, dd in NVdata:
271 ... print((n, dd))
272 (0, 'aqua')
273 (1, 'aqua')
274 (2, 'blue')
275 (8, 'red')
276 >>> NVdata[2] == NV[2] # NVdata gets 'color', NV gets datadict
277 False
278 """
279
280 __slots__ = ("_nodes",)
281
282 def __getstate__(self):
283 return {"_nodes": self._nodes}
284
285 def __setstate__(self, state):
286 self._nodes = state["_nodes"]
287
288 def __init__(self, graph):
289 self._nodes = graph._node
290
291 # Mapping methods
292 def __len__(self):
293 return len(self._nodes)
294
295 def __iter__(self):
296 return iter(self._nodes)
297
298 def __getitem__(self, n):
299 if isinstance(n, slice):
300 raise nx.NetworkXError(
301 f"{type(self).__name__} does not support slicing, "
302 f"try list(G.nodes)[{n.start}:{n.stop}:{n.step}]"
303 )
304 return self._nodes[n]
305
306 # Set methods
307 def __contains__(self, n):
308 return n in self._nodes
309
310 @classmethod
311 def _from_iterable(cls, it):
312 return set(it)
313
314 # DataView method
315 def __call__(self, data=False, default=None):
316 if data is False:
317 return self
318 return NodeDataView(self._nodes, data, default)
319
320 def data(self, data=True, default=None):
321 """
322 Return a read-only view of node data.
323
324 Parameters
325 ----------
326 data : bool or node data key, default=True
327 If ``data=True`` (the default), return a `NodeDataView` object that
328 maps each node to *all* of its attributes. `data` may also be an
329 arbitrary key, in which case the `NodeDataView` maps each node to
330 the value for the keyed attribute. In this case, if a node does
331 not have the `data` attribute, the `default` value is used.
332 default : object, default=None
333 The value used when a node does not have a specific attribute.
334
335 Returns
336 -------
337 NodeDataView
338 The layout of the returned NodeDataView depends on the value of the
339 `data` parameter.
340
341 Notes
342 -----
343 If ``data=False``, returns a `NodeView` object without data.
344
345 See Also
346 --------
347 NodeDataView
348
349 Examples
350 --------
351 >>> G = nx.Graph()
352 >>> G.add_nodes_from(
353 ... [
354 ... (0, {"color": "red", "weight": 10}),
355 ... (1, {"color": "blue"}),
356 ... (2, {"color": "yellow", "weight": 2}),
357 ... ]
358 ... )
359
360 Accessing node data with ``data=True`` (the default) returns a
361 NodeDataView mapping each node to all of its attributes:
362
363 >>> G.nodes.data()
364 NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}})
365
366 If `data` represents a key in the node attribute dict, a NodeDataView mapping
367 the nodes to the value for that specific key is returned:
368
369 >>> G.nodes.data("color")
370 NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color')
371
372 If a specific key is not found in an attribute dict, the value specified
373 by `default` is returned:
374
375 >>> G.nodes.data("weight", default=-999)
376 NodeDataView({0: 10, 1: -999, 2: 2}, data='weight')
377
378 Note that there is no check that the `data` key is in any of the
379 node attribute dictionaries:
380
381 >>> G.nodes.data("height")
382 NodeDataView({0: None, 1: None, 2: None}, data='height')
383 """
384 if data is False:
385 return self
386 return NodeDataView(self._nodes, data, default)
387
388 def __str__(self):
389 return str(list(self))
390
391 def __repr__(self):
392 return f"{self.__class__.__name__}({tuple(self)})"
393
394
395class NodeDataView(Set):
396 """A DataView class for nodes of a NetworkX Graph
397
398 The main use for this class is to iterate through node-data pairs.
399 The data can be the entire data-dictionary for each node, or it
400 can be a specific attribute (with default) for each node.
401 Set operations are enabled with NodeDataView, but don't work in
402 cases where the data is not hashable. Use with caution.
403 Typically, set operations on nodes use NodeView, not NodeDataView.
404 That is, they use `G.nodes` instead of `G.nodes(data='foo')`.
405
406 Parameters
407 ==========
408 graph : NetworkX graph-like class
409 data : bool or string (default=False)
410 default : object (default=None)
411 """
412
413 __slots__ = ("_nodes", "_data", "_default")
414
415 def __getstate__(self):
416 return {"_nodes": self._nodes, "_data": self._data, "_default": self._default}
417
418 def __setstate__(self, state):
419 self._nodes = state["_nodes"]
420 self._data = state["_data"]
421 self._default = state["_default"]
422
423 def __init__(self, nodedict, data=False, default=None):
424 self._nodes = nodedict
425 self._data = data
426 self._default = default
427
428 @classmethod
429 def _from_iterable(cls, it):
430 try:
431 return set(it)
432 except TypeError as err:
433 if "unhashable" in str(err):
434 msg = " : Could be b/c data=True or your values are unhashable"
435 raise TypeError(str(err) + msg) from err
436 raise
437
438 def __len__(self):
439 return len(self._nodes)
440
441 def __iter__(self):
442 data = self._data
443 if data is False:
444 return iter(self._nodes)
445 if data is True:
446 return iter(self._nodes.items())
447 return (
448 (n, dd[data] if data in dd else self._default)
449 for n, dd in self._nodes.items()
450 )
451
452 def __contains__(self, n):
453 try:
454 node_in = n in self._nodes
455 except TypeError:
456 n, d = n
457 return n in self._nodes and self[n] == d
458 if node_in is True:
459 return node_in
460 try:
461 n, d = n
462 except (TypeError, ValueError):
463 return False
464 return n in self._nodes and self[n] == d
465
466 def __getitem__(self, n):
467 if isinstance(n, slice):
468 raise nx.NetworkXError(
469 f"{type(self).__name__} does not support slicing, "
470 f"try list(G.nodes.data())[{n.start}:{n.stop}:{n.step}]"
471 )
472 ddict = self._nodes[n]
473 data = self._data
474 if data is False or data is True:
475 return ddict
476 return ddict[data] if data in ddict else self._default
477
478 def __str__(self):
479 return str(list(self))
480
481 def __repr__(self):
482 name = self.__class__.__name__
483 if self._data is False:
484 return f"{name}({tuple(self)})"
485 if self._data is True:
486 return f"{name}({dict(self)})"
487 return f"{name}({dict(self)}, data={self._data!r})"
488
489
490# DegreeViews
491class DiDegreeView:
492 """A View class for degree of nodes in a NetworkX Graph
493
494 The functionality is like dict.items() with (node, degree) pairs.
495 Additional functionality includes read-only lookup of node degree,
496 and calling with optional features nbunch (for only a subset of nodes)
497 and weight (use edge weights to compute degree).
498
499 Parameters
500 ==========
501 graph : NetworkX graph-like class
502 nbunch : node, container of nodes, or None meaning all nodes (default=None)
503 weight : bool or string (default=None)
504
505 Notes
506 -----
507 DegreeView can still lookup any node even if nbunch is specified.
508
509 Examples
510 --------
511 >>> G = nx.path_graph(3)
512 >>> DV = G.degree()
513 >>> assert DV[2] == 1
514 >>> assert sum(deg for n, deg in DV) == 4
515
516 >>> DVweight = G.degree(weight="span")
517 >>> G.add_edge(1, 2, span=34)
518 >>> DVweight[2]
519 34
520 >>> DVweight[0] # default edge weight is 1
521 1
522 >>> sum(span for n, span in DVweight) # sum weighted degrees
523 70
524
525 >>> DVnbunch = G.degree(nbunch=(1, 2))
526 >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
527 """
528
529 def __init__(self, G, nbunch=None, weight=None):
530 self._graph = G
531 self._succ = G._succ if hasattr(G, "_succ") else G._adj
532 self._pred = G._pred if hasattr(G, "_pred") else G._adj
533 self._nodes = self._succ if nbunch is None else list(G.nbunch_iter(nbunch))
534 self._weight = weight
535
536 def __call__(self, nbunch=None, weight=None):
537 if nbunch is None:
538 if weight == self._weight:
539 return self
540 return self.__class__(self._graph, None, weight)
541 try:
542 if nbunch in self._nodes:
543 if weight == self._weight:
544 return self[nbunch]
545 return self.__class__(self._graph, None, weight)[nbunch]
546 except TypeError:
547 pass
548 return self.__class__(self._graph, nbunch, weight)
549
550 def __getitem__(self, n):
551 weight = self._weight
552 succs = self._succ[n]
553 preds = self._pred[n]
554 if weight is None:
555 return len(succs) + len(preds)
556 return sum(dd.get(weight, 1) for dd in succs.values()) + sum(
557 dd.get(weight, 1) for dd in preds.values()
558 )
559
560 def __iter__(self):
561 weight = self._weight
562 if weight is None:
563 for n in self._nodes:
564 succs = self._succ[n]
565 preds = self._pred[n]
566 yield (n, len(succs) + len(preds))
567 else:
568 for n in self._nodes:
569 succs = self._succ[n]
570 preds = self._pred[n]
571 deg = sum(dd.get(weight, 1) for dd in succs.values()) + sum(
572 dd.get(weight, 1) for dd in preds.values()
573 )
574 yield (n, deg)
575
576 def __len__(self):
577 return len(self._nodes)
578
579 def __str__(self):
580 return str(list(self))
581
582 def __repr__(self):
583 return f"{self.__class__.__name__}({dict(self)})"
584
585
586class DegreeView(DiDegreeView):
587 """A DegreeView class to act as G.degree for a NetworkX Graph
588
589 Typical usage focuses on iteration over `(node, degree)` pairs.
590 The degree is by default the number of edges incident to the node.
591 Optional argument `weight` enables weighted degree using the edge
592 attribute named in the `weight` argument. Reporting and iteration
593 can also be restricted to a subset of nodes using `nbunch`.
594
595 Additional functionality include node lookup so that `G.degree[n]`
596 reported the (possibly weighted) degree of node `n`. Calling the
597 view creates a view with different arguments `nbunch` or `weight`.
598
599 Parameters
600 ==========
601 graph : NetworkX graph-like class
602 nbunch : node, container of nodes, or None meaning all nodes (default=None)
603 weight : string or None (default=None)
604
605 Notes
606 -----
607 DegreeView can still lookup any node even if nbunch is specified.
608
609 Examples
610 --------
611 >>> G = nx.path_graph(3)
612 >>> DV = G.degree()
613 >>> assert DV[2] == 1
614 >>> assert G.degree[2] == 1
615 >>> assert sum(deg for n, deg in DV) == 4
616
617 >>> DVweight = G.degree(weight="span")
618 >>> G.add_edge(1, 2, span=34)
619 >>> DVweight[2]
620 34
621 >>> DVweight[0] # default edge weight is 1
622 1
623 >>> sum(span for n, span in DVweight) # sum weighted degrees
624 70
625
626 >>> DVnbunch = G.degree(nbunch=(1, 2))
627 >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
628 """
629
630 def __getitem__(self, n):
631 weight = self._weight
632 nbrs = self._succ[n]
633 if weight is None:
634 return len(nbrs) + (n in nbrs)
635 return sum(dd.get(weight, 1) for dd in nbrs.values()) + (
636 n in nbrs and nbrs[n].get(weight, 1)
637 )
638
639 def __iter__(self):
640 weight = self._weight
641 if weight is None:
642 for n in self._nodes:
643 nbrs = self._succ[n]
644 yield (n, len(nbrs) + (n in nbrs))
645 else:
646 for n in self._nodes:
647 nbrs = self._succ[n]
648 deg = sum(dd.get(weight, 1) for dd in nbrs.values()) + (
649 n in nbrs and nbrs[n].get(weight, 1)
650 )
651 yield (n, deg)
652
653
654class OutDegreeView(DiDegreeView):
655 """A DegreeView class to report out_degree for a DiGraph; See DegreeView"""
656
657 def __getitem__(self, n):
658 weight = self._weight
659 nbrs = self._succ[n]
660 if self._weight is None:
661 return len(nbrs)
662 return sum(dd.get(self._weight, 1) for dd in nbrs.values())
663
664 def __iter__(self):
665 weight = self._weight
666 if weight is None:
667 for n in self._nodes:
668 succs = self._succ[n]
669 yield (n, len(succs))
670 else:
671 for n in self._nodes:
672 succs = self._succ[n]
673 deg = sum(dd.get(weight, 1) for dd in succs.values())
674 yield (n, deg)
675
676
677class InDegreeView(DiDegreeView):
678 """A DegreeView class to report in_degree for a DiGraph; See DegreeView"""
679
680 def __getitem__(self, n):
681 weight = self._weight
682 nbrs = self._pred[n]
683 if weight is None:
684 return len(nbrs)
685 return sum(dd.get(weight, 1) for dd in nbrs.values())
686
687 def __iter__(self):
688 weight = self._weight
689 if weight is None:
690 for n in self._nodes:
691 preds = self._pred[n]
692 yield (n, len(preds))
693 else:
694 for n in self._nodes:
695 preds = self._pred[n]
696 deg = sum(dd.get(weight, 1) for dd in preds.values())
697 yield (n, deg)
698
699
700class MultiDegreeView(DiDegreeView):
701 """A DegreeView class for undirected multigraphs; See DegreeView"""
702
703 def __getitem__(self, n):
704 weight = self._weight
705 nbrs = self._succ[n]
706 if weight is None:
707 return sum(len(keys) for keys in nbrs.values()) + (
708 n in nbrs and len(nbrs[n])
709 )
710 # edge weighted graph - degree is sum of nbr edge weights
711 deg = sum(
712 d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
713 )
714 if n in nbrs:
715 deg += sum(d.get(weight, 1) for d in nbrs[n].values())
716 return deg
717
718 def __iter__(self):
719 weight = self._weight
720 if weight is None:
721 for n in self._nodes:
722 nbrs = self._succ[n]
723 deg = sum(len(keys) for keys in nbrs.values()) + (
724 n in nbrs and len(nbrs[n])
725 )
726 yield (n, deg)
727 else:
728 for n in self._nodes:
729 nbrs = self._succ[n]
730 deg = sum(
731 d.get(weight, 1)
732 for key_dict in nbrs.values()
733 for d in key_dict.values()
734 )
735 if n in nbrs:
736 deg += sum(d.get(weight, 1) for d in nbrs[n].values())
737 yield (n, deg)
738
739
740class DiMultiDegreeView(DiDegreeView):
741 """A DegreeView class for MultiDiGraph; See DegreeView"""
742
743 def __getitem__(self, n):
744 weight = self._weight
745 succs = self._succ[n]
746 preds = self._pred[n]
747 if weight is None:
748 return sum(len(keys) for keys in succs.values()) + sum(
749 len(keys) for keys in preds.values()
750 )
751 # edge weighted graph - degree is sum of nbr edge weights
752 deg = sum(
753 d.get(weight, 1) for key_dict in succs.values() for d in key_dict.values()
754 ) + sum(
755 d.get(weight, 1) for key_dict in preds.values() for d in key_dict.values()
756 )
757 return deg
758
759 def __iter__(self):
760 weight = self._weight
761 if weight is None:
762 for n in self._nodes:
763 succs = self._succ[n]
764 preds = self._pred[n]
765 deg = sum(len(keys) for keys in succs.values()) + sum(
766 len(keys) for keys in preds.values()
767 )
768 yield (n, deg)
769 else:
770 for n in self._nodes:
771 succs = self._succ[n]
772 preds = self._pred[n]
773 deg = sum(
774 d.get(weight, 1)
775 for key_dict in succs.values()
776 for d in key_dict.values()
777 ) + sum(
778 d.get(weight, 1)
779 for key_dict in preds.values()
780 for d in key_dict.values()
781 )
782 yield (n, deg)
783
784
785class InMultiDegreeView(DiDegreeView):
786 """A DegreeView class for inward degree of MultiDiGraph; See DegreeView"""
787
788 def __getitem__(self, n):
789 weight = self._weight
790 nbrs = self._pred[n]
791 if weight is None:
792 return sum(len(data) for data in nbrs.values())
793 # edge weighted graph - degree is sum of nbr edge weights
794 return sum(
795 d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
796 )
797
798 def __iter__(self):
799 weight = self._weight
800 if weight is None:
801 for n in self._nodes:
802 nbrs = self._pred[n]
803 deg = sum(len(data) for data in nbrs.values())
804 yield (n, deg)
805 else:
806 for n in self._nodes:
807 nbrs = self._pred[n]
808 deg = sum(
809 d.get(weight, 1)
810 for key_dict in nbrs.values()
811 for d in key_dict.values()
812 )
813 yield (n, deg)
814
815
816class OutMultiDegreeView(DiDegreeView):
817 """A DegreeView class for outward degree of MultiDiGraph; See DegreeView"""
818
819 def __getitem__(self, n):
820 weight = self._weight
821 nbrs = self._succ[n]
822 if weight is None:
823 return sum(len(data) for data in nbrs.values())
824 # edge weighted graph - degree is sum of nbr edge weights
825 return sum(
826 d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
827 )
828
829 def __iter__(self):
830 weight = self._weight
831 if weight is None:
832 for n in self._nodes:
833 nbrs = self._succ[n]
834 deg = sum(len(data) for data in nbrs.values())
835 yield (n, deg)
836 else:
837 for n in self._nodes:
838 nbrs = self._succ[n]
839 deg = sum(
840 d.get(weight, 1)
841 for key_dict in nbrs.values()
842 for d in key_dict.values()
843 )
844 yield (n, deg)
845
846
847# A base class for all edge views. Ensures all edge view and edge data view
848# objects/classes are captured by `isinstance(obj, EdgeViewABC)` and
849# `issubclass(cls, EdgeViewABC)` respectively
850class EdgeViewABC(ABC):
851 pass
852
853
854# EdgeDataViews
855class OutEdgeDataView(EdgeViewABC):
856 """EdgeDataView for outward edges of DiGraph; See EdgeDataView"""
857
858 __slots__ = (
859 "_viewer",
860 "_nbunch",
861 "_data",
862 "_default",
863 "_adjdict",
864 "_nodes_nbrs",
865 "_report",
866 )
867
868 def __getstate__(self):
869 return {
870 "viewer": self._viewer,
871 "nbunch": self._nbunch,
872 "data": self._data,
873 "default": self._default,
874 }
875
876 def __setstate__(self, state):
877 self.__init__(**state)
878
879 def __init__(self, viewer, nbunch=None, data=False, *, default=None):
880 self._viewer = viewer
881 adjdict = self._adjdict = viewer._adjdict
882 if nbunch is None:
883 self._nodes_nbrs = adjdict.items
884 else:
885 # dict retains order of nodes but acts like a set
886 nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
887 self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
888 self._nbunch = nbunch
889 self._data = data
890 self._default = default
891 # Set _report based on data and default
892 if data is True:
893 self._report = lambda n, nbr, dd: (n, nbr, dd)
894 elif data is False:
895 self._report = lambda n, nbr, dd: (n, nbr)
896 else: # data is attribute name
897 self._report = (
898 lambda n, nbr, dd: (n, nbr, dd[data])
899 if data in dd
900 else (n, nbr, default)
901 )
902
903 def __len__(self):
904 return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
905
906 def __iter__(self):
907 return (
908 self._report(n, nbr, dd)
909 for n, nbrs in self._nodes_nbrs()
910 for nbr, dd in nbrs.items()
911 )
912
913 def __contains__(self, e):
914 u, v = e[:2]
915 if self._nbunch is not None and u not in self._nbunch:
916 return False # this edge doesn't start in nbunch
917 try:
918 ddict = self._adjdict[u][v]
919 except KeyError:
920 return False
921 return e == self._report(u, v, ddict)
922
923 def __str__(self):
924 return str(list(self))
925
926 def __repr__(self):
927 return f"{self.__class__.__name__}({list(self)})"
928
929
930class EdgeDataView(OutEdgeDataView):
931 """A EdgeDataView class for edges of Graph
932
933 This view is primarily used to iterate over the edges reporting
934 edges as node-tuples with edge data optionally reported. The
935 argument `nbunch` allows restriction to edges incident to nodes
936 in that container/singleton. The default (nbunch=None)
937 reports all edges. The arguments `data` and `default` control
938 what edge data is reported. The default `data is False` reports
939 only node-tuples for each edge. If `data is True` the entire edge
940 data dict is returned. Otherwise `data` is assumed to hold the name
941 of the edge attribute to report with default `default` if that
942 edge attribute is not present.
943
944 Parameters
945 ----------
946 nbunch : container of nodes, node or None (default None)
947 data : False, True or string (default False)
948 default : default value (default None)
949
950 Examples
951 --------
952 >>> G = nx.path_graph(3)
953 >>> G.add_edge(1, 2, foo="bar")
954 >>> list(G.edges(data="foo", default="biz"))
955 [(0, 1, 'biz'), (1, 2, 'bar')]
956 >>> assert (0, 1, "biz") in G.edges(data="foo", default="biz")
957 """
958
959 __slots__ = ()
960
961 def __len__(self):
962 return sum(1 for e in self)
963
964 def __iter__(self):
965 seen = {}
966 for n, nbrs in self._nodes_nbrs():
967 for nbr, dd in nbrs.items():
968 if nbr not in seen:
969 yield self._report(n, nbr, dd)
970 seen[n] = 1
971 del seen
972
973 def __contains__(self, e):
974 u, v = e[:2]
975 if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
976 return False # this edge doesn't start and it doesn't end in nbunch
977 try:
978 ddict = self._adjdict[u][v]
979 except KeyError:
980 return False
981 return e == self._report(u, v, ddict)
982
983
984class InEdgeDataView(OutEdgeDataView):
985 """An EdgeDataView class for outward edges of DiGraph; See EdgeDataView"""
986
987 __slots__ = ()
988
989 def __iter__(self):
990 return (
991 self._report(nbr, n, dd)
992 for n, nbrs in self._nodes_nbrs()
993 for nbr, dd in nbrs.items()
994 )
995
996 def __contains__(self, e):
997 u, v = e[:2]
998 if self._nbunch is not None and v not in self._nbunch:
999 return False # this edge doesn't end in nbunch
1000 try:
1001 ddict = self._adjdict[v][u]
1002 except KeyError:
1003 return False
1004 return e == self._report(u, v, ddict)
1005
1006
1007class OutMultiEdgeDataView(OutEdgeDataView):
1008 """An EdgeDataView for outward edges of MultiDiGraph; See EdgeDataView"""
1009
1010 __slots__ = ("keys",)
1011
1012 def __getstate__(self):
1013 return {
1014 "viewer": self._viewer,
1015 "nbunch": self._nbunch,
1016 "keys": self.keys,
1017 "data": self._data,
1018 "default": self._default,
1019 }
1020
1021 def __setstate__(self, state):
1022 self.__init__(**state)
1023
1024 def __init__(self, viewer, nbunch=None, data=False, *, default=None, keys=False):
1025 self._viewer = viewer
1026 adjdict = self._adjdict = viewer._adjdict
1027 self.keys = keys
1028 if nbunch is None:
1029 self._nodes_nbrs = adjdict.items
1030 else:
1031 # dict retains order of nodes but acts like a set
1032 nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
1033 self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
1034 self._nbunch = nbunch
1035 self._data = data
1036 self._default = default
1037 # Set _report based on data and default
1038 if data is True:
1039 if keys is True:
1040 self._report = lambda n, nbr, k, dd: (n, nbr, k, dd)
1041 else:
1042 self._report = lambda n, nbr, k, dd: (n, nbr, dd)
1043 elif data is False:
1044 if keys is True:
1045 self._report = lambda n, nbr, k, dd: (n, nbr, k)
1046 else:
1047 self._report = lambda n, nbr, k, dd: (n, nbr)
1048 else: # data is attribute name
1049 if keys is True:
1050 self._report = (
1051 lambda n, nbr, k, dd: (n, nbr, k, dd[data])
1052 if data in dd
1053 else (n, nbr, k, default)
1054 )
1055 else:
1056 self._report = (
1057 lambda n, nbr, k, dd: (n, nbr, dd[data])
1058 if data in dd
1059 else (n, nbr, default)
1060 )
1061
1062 def __len__(self):
1063 return sum(1 for e in self)
1064
1065 def __iter__(self):
1066 return (
1067 self._report(n, nbr, k, dd)
1068 for n, nbrs in self._nodes_nbrs()
1069 for nbr, kd in nbrs.items()
1070 for k, dd in kd.items()
1071 )
1072
1073 def __contains__(self, e):
1074 u, v = e[:2]
1075 if self._nbunch is not None and u not in self._nbunch:
1076 return False # this edge doesn't start in nbunch
1077 try:
1078 kdict = self._adjdict[u][v]
1079 except KeyError:
1080 return False
1081 if self.keys is True:
1082 k = e[2]
1083 try:
1084 dd = kdict[k]
1085 except KeyError:
1086 return False
1087 return e == self._report(u, v, k, dd)
1088 return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1089
1090
1091class MultiEdgeDataView(OutMultiEdgeDataView):
1092 """An EdgeDataView class for edges of MultiGraph; See EdgeDataView"""
1093
1094 __slots__ = ()
1095
1096 def __iter__(self):
1097 seen = {}
1098 for n, nbrs in self._nodes_nbrs():
1099 for nbr, kd in nbrs.items():
1100 if nbr not in seen:
1101 for k, dd in kd.items():
1102 yield self._report(n, nbr, k, dd)
1103 seen[n] = 1
1104 del seen
1105
1106 def __contains__(self, e):
1107 u, v = e[:2]
1108 if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
1109 return False # this edge doesn't start and doesn't end in nbunch
1110 try:
1111 kdict = self._adjdict[u][v]
1112 except KeyError:
1113 try:
1114 kdict = self._adjdict[v][u]
1115 except KeyError:
1116 return False
1117 if self.keys is True:
1118 k = e[2]
1119 try:
1120 dd = kdict[k]
1121 except KeyError:
1122 return False
1123 return e == self._report(u, v, k, dd)
1124 return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1125
1126
1127class InMultiEdgeDataView(OutMultiEdgeDataView):
1128 """An EdgeDataView for inward edges of MultiDiGraph; See EdgeDataView"""
1129
1130 __slots__ = ()
1131
1132 def __iter__(self):
1133 return (
1134 self._report(nbr, n, k, dd)
1135 for n, nbrs in self._nodes_nbrs()
1136 for nbr, kd in nbrs.items()
1137 for k, dd in kd.items()
1138 )
1139
1140 def __contains__(self, e):
1141 u, v = e[:2]
1142 if self._nbunch is not None and v not in self._nbunch:
1143 return False # this edge doesn't end in nbunch
1144 try:
1145 kdict = self._adjdict[v][u]
1146 except KeyError:
1147 return False
1148 if self.keys is True:
1149 k = e[2]
1150 dd = kdict[k]
1151 return e == self._report(u, v, k, dd)
1152 return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1153
1154
1155# EdgeViews have set operations and no data reported
1156class OutEdgeView(Set, Mapping, EdgeViewABC):
1157 """A EdgeView class for outward edges of a DiGraph"""
1158
1159 __slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
1160
1161 def __getstate__(self):
1162 return {"_graph": self._graph, "_adjdict": self._adjdict}
1163
1164 def __setstate__(self, state):
1165 self._graph = state["_graph"]
1166 self._adjdict = state["_adjdict"]
1167 self._nodes_nbrs = self._adjdict.items
1168
1169 @classmethod
1170 def _from_iterable(cls, it):
1171 return set(it)
1172
1173 dataview = OutEdgeDataView
1174
1175 def __init__(self, G):
1176 self._graph = G
1177 self._adjdict = G._succ if hasattr(G, "succ") else G._adj
1178 self._nodes_nbrs = self._adjdict.items
1179
1180 # Set methods
1181 def __len__(self):
1182 return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
1183
1184 def __iter__(self):
1185 for n, nbrs in self._nodes_nbrs():
1186 for nbr in nbrs:
1187 yield (n, nbr)
1188
1189 def __contains__(self, e):
1190 try:
1191 u, v = e
1192 return v in self._adjdict[u]
1193 except KeyError:
1194 return False
1195
1196 # Mapping Methods
1197 def __getitem__(self, e):
1198 if isinstance(e, slice):
1199 raise nx.NetworkXError(
1200 f"{type(self).__name__} does not support slicing, "
1201 f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
1202 )
1203 u, v = e
1204 try:
1205 return self._adjdict[u][v]
1206 except KeyError as ex: # Customize msg to indicate exception origin
1207 raise KeyError(f"The edge {e} is not in the graph.")
1208
1209 # EdgeDataView methods
1210 def __call__(self, nbunch=None, data=False, *, default=None):
1211 if nbunch is None and data is False:
1212 return self
1213 return self.dataview(self, nbunch, data, default=default)
1214
1215 def data(self, data=True, default=None, nbunch=None):
1216 """
1217 Return a read-only view of edge data.
1218
1219 Parameters
1220 ----------
1221 data : bool or edge attribute key
1222 If ``data=True``, then the data view maps each edge to a dictionary
1223 containing all of its attributes. If `data` is a key in the edge
1224 dictionary, then the data view maps each edge to its value for
1225 the keyed attribute. In this case, if the edge doesn't have the
1226 attribute, the `default` value is returned.
1227 default : object, default=None
1228 The value used when an edge does not have a specific attribute
1229 nbunch : container of nodes, optional (default=None)
1230 Allows restriction to edges only involving certain nodes. All edges
1231 are considered by default.
1232
1233 Returns
1234 -------
1235 dataview
1236 Returns an `EdgeDataView` for undirected Graphs, `OutEdgeDataView`
1237 for DiGraphs, `MultiEdgeDataView` for MultiGraphs and
1238 `OutMultiEdgeDataView` for MultiDiGraphs.
1239
1240 Notes
1241 -----
1242 If ``data=False``, returns an `EdgeView` without any edge data.
1243
1244 See Also
1245 --------
1246 EdgeDataView
1247 OutEdgeDataView
1248 MultiEdgeDataView
1249 OutMultiEdgeDataView
1250
1251 Examples
1252 --------
1253 >>> G = nx.Graph()
1254 >>> G.add_edges_from(
1255 ... [
1256 ... (0, 1, {"dist": 3, "capacity": 20}),
1257 ... (1, 2, {"dist": 4}),
1258 ... (2, 0, {"dist": 5}),
1259 ... ]
1260 ... )
1261
1262 Accessing edge data with ``data=True`` (the default) returns an
1263 edge data view object listing each edge with all of its attributes:
1264
1265 >>> G.edges.data()
1266 EdgeDataView([(0, 1, {'dist': 3, 'capacity': 20}), (0, 2, {'dist': 5}), (1, 2, {'dist': 4})])
1267
1268 If `data` represents a key in the edge attribute dict, a dataview listing
1269 each edge with its value for that specific key is returned:
1270
1271 >>> G.edges.data("dist")
1272 EdgeDataView([(0, 1, 3), (0, 2, 5), (1, 2, 4)])
1273
1274 `nbunch` can be used to limit the edges:
1275
1276 >>> G.edges.data("dist", nbunch=[0])
1277 EdgeDataView([(0, 1, 3), (0, 2, 5)])
1278
1279 If a specific key is not found in an edge attribute dict, the value
1280 specified by `default` is used:
1281
1282 >>> G.edges.data("capacity")
1283 EdgeDataView([(0, 1, 20), (0, 2, None), (1, 2, None)])
1284
1285 Note that there is no check that the `data` key is present in any of
1286 the edge attribute dictionaries:
1287
1288 >>> G.edges.data("speed")
1289 EdgeDataView([(0, 1, None), (0, 2, None), (1, 2, None)])
1290 """
1291 if nbunch is None and data is False:
1292 return self
1293 return self.dataview(self, nbunch, data, default=default)
1294
1295 # String Methods
1296 def __str__(self):
1297 return str(list(self))
1298
1299 def __repr__(self):
1300 return f"{self.__class__.__name__}({list(self)})"
1301
1302
1303class EdgeView(OutEdgeView):
1304 """A EdgeView class for edges of a Graph
1305
1306 This densely packed View allows iteration over edges, data lookup
1307 like a dict and set operations on edges represented by node-tuples.
1308 In addition, edge data can be controlled by calling this object
1309 possibly creating an EdgeDataView. Typically edges are iterated over
1310 and reported as `(u, v)` node tuples or `(u, v, key)` node/key tuples
1311 for multigraphs. Those edge representations can also be using to
1312 lookup the data dict for any edge. Set operations also are available
1313 where those tuples are the elements of the set.
1314 Calling this object with optional arguments `data`, `default` and `keys`
1315 controls the form of the tuple (see EdgeDataView). Optional argument
1316 `nbunch` allows restriction to edges only involving certain nodes.
1317
1318 If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
1319 If `data is True` iterate over 3-tuples `(u, v, datadict)`.
1320 Otherwise iterate over `(u, v, datadict.get(data, default))`.
1321 For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key` above.
1322
1323 Parameters
1324 ==========
1325 graph : NetworkX graph-like class
1326 nbunch : (default= all nodes in graph) only report edges with these nodes
1327 keys : (only for MultiGraph. default=False) report edge key in tuple
1328 data : bool or string (default=False) see above
1329 default : object (default=None)
1330
1331 Examples
1332 ========
1333 >>> G = nx.path_graph(4)
1334 >>> EV = G.edges()
1335 >>> (2, 3) in EV
1336 True
1337 >>> for u, v in EV:
1338 ... print((u, v))
1339 (0, 1)
1340 (1, 2)
1341 (2, 3)
1342 >>> assert EV & {(1, 2), (3, 4)} == {(1, 2)}
1343
1344 >>> EVdata = G.edges(data="color", default="aqua")
1345 >>> G.add_edge(2, 3, color="blue")
1346 >>> assert (2, 3, "blue") in EVdata
1347 >>> for u, v, c in EVdata:
1348 ... print(f"({u}, {v}) has color: {c}")
1349 (0, 1) has color: aqua
1350 (1, 2) has color: aqua
1351 (2, 3) has color: blue
1352
1353 >>> EVnbunch = G.edges(nbunch=2)
1354 >>> assert (2, 3) in EVnbunch
1355 >>> assert (0, 1) not in EVnbunch
1356 >>> for u, v in EVnbunch:
1357 ... assert u == 2 or v == 2
1358
1359 >>> MG = nx.path_graph(4, create_using=nx.MultiGraph)
1360 >>> EVmulti = MG.edges(keys=True)
1361 >>> (2, 3, 0) in EVmulti
1362 True
1363 >>> (2, 3) in EVmulti # 2-tuples work even when keys is True
1364 True
1365 >>> key = MG.add_edge(2, 3)
1366 >>> for u, v, k in EVmulti:
1367 ... print((u, v, k))
1368 (0, 1, 0)
1369 (1, 2, 0)
1370 (2, 3, 0)
1371 (2, 3, 1)
1372 """
1373
1374 __slots__ = ()
1375
1376 dataview = EdgeDataView
1377
1378 def __len__(self):
1379 num_nbrs = (len(nbrs) + (n in nbrs) for n, nbrs in self._nodes_nbrs())
1380 return sum(num_nbrs) // 2
1381
1382 def __iter__(self):
1383 seen = {}
1384 for n, nbrs in self._nodes_nbrs():
1385 for nbr in list(nbrs):
1386 if nbr not in seen:
1387 yield (n, nbr)
1388 seen[n] = 1
1389 del seen
1390
1391 def __contains__(self, e):
1392 try:
1393 u, v = e[:2]
1394 return v in self._adjdict[u] or u in self._adjdict[v]
1395 except (KeyError, ValueError):
1396 return False
1397
1398
1399class InEdgeView(OutEdgeView):
1400 """A EdgeView class for inward edges of a DiGraph"""
1401
1402 __slots__ = ()
1403
1404 def __setstate__(self, state):
1405 self._graph = state["_graph"]
1406 self._adjdict = state["_adjdict"]
1407 self._nodes_nbrs = self._adjdict.items
1408
1409 dataview = InEdgeDataView
1410
1411 def __init__(self, G):
1412 self._graph = G
1413 self._adjdict = G._pred if hasattr(G, "pred") else G._adj
1414 self._nodes_nbrs = self._adjdict.items
1415
1416 def __iter__(self):
1417 for n, nbrs in self._nodes_nbrs():
1418 for nbr in nbrs:
1419 yield (nbr, n)
1420
1421 def __contains__(self, e):
1422 try:
1423 u, v = e
1424 return u in self._adjdict[v]
1425 except KeyError:
1426 return False
1427
1428 def __getitem__(self, e):
1429 if isinstance(e, slice):
1430 raise nx.NetworkXError(
1431 f"{type(self).__name__} does not support slicing, "
1432 f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
1433 )
1434 u, v = e
1435 return self._adjdict[v][u]
1436
1437
1438class OutMultiEdgeView(OutEdgeView):
1439 """A EdgeView class for outward edges of a MultiDiGraph"""
1440
1441 __slots__ = ()
1442
1443 dataview = OutMultiEdgeDataView
1444
1445 def __len__(self):
1446 return sum(
1447 len(kdict) for n, nbrs in self._nodes_nbrs() for nbr, kdict in nbrs.items()
1448 )
1449
1450 def __iter__(self):
1451 for n, nbrs in self._nodes_nbrs():
1452 for nbr, kdict in nbrs.items():
1453 for key in kdict:
1454 yield (n, nbr, key)
1455
1456 def __contains__(self, e):
1457 N = len(e)
1458 if N == 3:
1459 u, v, k = e
1460 elif N == 2:
1461 u, v = e
1462 k = 0
1463 else:
1464 raise ValueError("MultiEdge must have length 2 or 3")
1465 try:
1466 return k in self._adjdict[u][v]
1467 except KeyError:
1468 return False
1469
1470 def __getitem__(self, e):
1471 if isinstance(e, slice):
1472 raise nx.NetworkXError(
1473 f"{type(self).__name__} does not support slicing, "
1474 f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
1475 )
1476 u, v, k = e
1477 return self._adjdict[u][v][k]
1478
1479 def __call__(self, nbunch=None, data=False, *, default=None, keys=False):
1480 if nbunch is None and data is False and keys is True:
1481 return self
1482 return self.dataview(self, nbunch, data, default=default, keys=keys)
1483
1484 def data(self, data=True, default=None, nbunch=None, keys=False):
1485 if nbunch is None and data is False and keys is True:
1486 return self
1487 return self.dataview(self, nbunch, data, default=default, keys=keys)
1488
1489
1490class MultiEdgeView(OutMultiEdgeView):
1491 """A EdgeView class for edges of a MultiGraph"""
1492
1493 __slots__ = ()
1494
1495 dataview = MultiEdgeDataView
1496
1497 def __len__(self):
1498 return sum(1 for e in self)
1499
1500 def __iter__(self):
1501 seen = {}
1502 for n, nbrs in self._nodes_nbrs():
1503 for nbr, kd in nbrs.items():
1504 if nbr not in seen:
1505 for k, dd in kd.items():
1506 yield (n, nbr, k)
1507 seen[n] = 1
1508 del seen
1509
1510
1511class InMultiEdgeView(OutMultiEdgeView):
1512 """A EdgeView class for inward edges of a MultiDiGraph"""
1513
1514 __slots__ = ()
1515
1516 def __setstate__(self, state):
1517 self._graph = state["_graph"]
1518 self._adjdict = state["_adjdict"]
1519 self._nodes_nbrs = self._adjdict.items
1520
1521 dataview = InMultiEdgeDataView
1522
1523 def __init__(self, G):
1524 self._graph = G
1525 self._adjdict = G._pred if hasattr(G, "pred") else G._adj
1526 self._nodes_nbrs = self._adjdict.items
1527
1528 def __iter__(self):
1529 for n, nbrs in self._nodes_nbrs():
1530 for nbr, kdict in nbrs.items():
1531 for key in kdict:
1532 yield (nbr, n, key)
1533
1534 def __contains__(self, e):
1535 N = len(e)
1536 if N == 3:
1537 u, v, k = e
1538 elif N == 2:
1539 u, v = e
1540 k = 0
1541 else:
1542 raise ValueError("MultiEdge must have length 2 or 3")
1543 try:
1544 return k in self._adjdict[v][u]
1545 except KeyError:
1546 return False
1547
1548 def __getitem__(self, e):
1549 if isinstance(e, slice):
1550 raise nx.NetworkXError(
1551 f"{type(self).__name__} does not support slicing, "
1552 f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
1553 )
1554 u, v, k = e
1555 return self._adjdict[v][u][k]