diff options
| author | Dan Schult <dschult@colgate.edu> | 2018-07-20 00:53:31 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-07-20 00:53:31 -0400 |
| commit | e46f4c1cc02feaa08c855ca211ef114acc8fd772 (patch) | |
| tree | 4a39727be298fff1a2e1dc86906cc1961fd6abc5 /networkx/classes | |
| parent | cdbf49ee375a836cc16121dcd75334d9fda1cf18 (diff) | |
| download | networkx-e46f4c1cc02feaa08c855ca211ef114acc8fd772.tar.gz | |
Simplify the Graphview and SubGraphView system (#3073)
* Simplify the Graphview and SubGraphView system
- add tests to show that extensions of base graph classes (only add new functions)
should be able to use the Graph.subgraph and Graph.copy methods easily
- Remove ReadOnlyGraph class in favor of existing freeze() function
- Switch all GraphView classes to generic_graph_view function
- Switch all SubGraph classes to subgraph_view function
- Introduce deprecated functions that act like the deprecated classes.
Still need to:
- add docs
- add tests
- make sure backward compatible and marked as deprecated
- remove GraphView and SubGraph construct from rest of codebase
- update release docs
Fixes #2889
Fixes #2793
Fixes #2796
Fixes #2741
* Ease subclassing for to_(un)directed
- add to_directed_class indicator functions to base classes
- start deprecating G.fresh_copy
- update function.to(un)directed
* Remove G.fresh_copy from code replace with __class__
Add deprecation warnings for GraphView classes, ReverseView and
SubGraph. Also for fresh_copy function.
Diffstat (limited to 'networkx/classes')
| -rw-r--r-- | networkx/classes/coreviews.py | 22 | ||||
| -rw-r--r-- | networkx/classes/digraph.py | 182 | ||||
| -rw-r--r-- | networkx/classes/function.py | 59 | ||||
| -rw-r--r-- | networkx/classes/graph.py | 73 | ||||
| -rw-r--r-- | networkx/classes/graphviews.py | 295 | ||||
| -rw-r--r-- | networkx/classes/multidigraph.py | 189 | ||||
| -rw-r--r-- | networkx/classes/multigraph.py | 128 | ||||
| -rw-r--r-- | networkx/classes/ordered.py | 40 | ||||
| -rw-r--r-- | networkx/classes/tests/test_graphviews.py | 67 | ||||
| -rw-r--r-- | networkx/classes/tests/test_subgraphviews.py | 18 |
10 files changed, 345 insertions, 728 deletions
diff --git a/networkx/classes/coreviews.py b/networkx/classes/coreviews.py index 4365e386..1020a1fd 100644 --- a/networkx/classes/coreviews.py +++ b/networkx/classes/coreviews.py @@ -18,7 +18,6 @@ __all__ = ['AtlasView', 'AdjacencyView', 'MultiAdjacencyView', 'UnionMultiInner', 'UnionMultiAdjacency', 'FilterAtlas', 'FilterAdjacency', 'FilterMultiInner', 'FilterMultiAdjacency', - 'ReadOnlyGraph', ] @@ -259,27 +258,6 @@ class UnionMultiAdjacency(UnionAdjacency): return UnionMultiInner(self._succ[node], self._pred[node]) -class ReadOnlyGraph(object): - """A Mixin Class to mask the write methods of a graph class.""" - - def not_allowed(self, *args, **kwds): - msg = "SubGraph Views are readonly. Mutations not allowed" - raise nx.NetworkXError(msg) - - add_node = not_allowed - remove_node = not_allowed - add_nodes_from = not_allowed - remove_nodes_from = not_allowed - - add_edge = not_allowed - remove_edge = not_allowed - add_edges_from = not_allowed - add_weighted_edges_from = not_allowed - remove_edges_from = not_allowed - - clear = not_allowed - - class FilterAtlas(Mapping): # nodedict, nbrdict, keydict def __init__(self, d, NODE_OK): self._atlas = d diff --git a/networkx/classes/digraph.py b/networkx/classes/digraph.py index 887bafbc..60a202ff 100644 --- a/networkx/classes/digraph.py +++ b/networkx/classes/digraph.py @@ -217,6 +217,20 @@ class DiGraph(Graph): dict which holds attrbute values keyed by attribute name. It should require no arguments and return a dict-like object. + Typically, if your extension doesn't impact the data structure all + methods will inherited without issue except: `to_directed/to_undirected`. + By default these methods create a DiGraph/Graph class and you probably + want them to create your extension of a DiGraph/Graph. To facilitate + this we define two class variables that you can set in your subclass. + + to_directed_class : callable, (default: DiGraph or MultiDiGraph) + Class to create a new graph structure in the `to_directed` method. + If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used. + + to_undirected_class : callable, (default: Graph or MultiGraph) + Class to create a new graph structure in the `to_undirected` method. + If `None`, a NetworkX class (Graph or MultiGraph) is used. + Examples -------- @@ -1037,107 +1051,6 @@ class DiGraph(Graph): """Return True if graph is directed, False otherwise.""" return True - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - - Notes - ----- - If you subclass the base class you should overwrite this method - to return your class of graph. - """ - return DiGraph() - - def copy(self, as_view=False): - """Return a copy of the graph. - - The copy method by default returns an independent shallow copy - of the graph and attributes. That is, if an attribute is a - container, that container is shared by the original an the copy. - Use Python's `copy.deepcopy` for new containers. - - If `as_view` is True then a view is returned instead of a copy. - - Notes - ----- - All copies reproduce the graph structure, but data attributes - may be handled in different ways. There are four types of copies - of a graph that people might want. - - Deepcopy -- A "deepcopy" copies the graph structure as well as - all data attributes and any objects they might contain. - The entire graph object is new so that changes in the copy - do not affect the original object. (see Python's copy.deepcopy) - - Data Reference (Shallow) -- For a shallow copy the graph structure - is copied but the edge, node and graph attribute dicts are - references to those in the original graph. This saves - time and memory but could cause confusion if you change an attribute - in one graph and it changes the attribute in the other. - NetworkX does not provide this level of shallow copy. - - Independent Shallow -- This copy creates new independent attribute - dicts and then does a shallow copy of the attributes. That is, any - attributes that are containers are shared between the new graph - and the original. This is exactly what `dict.copy()` provides. - You can obtain this style copy using: - - >>> G = nx.path_graph(5) - >>> H = G.copy() - >>> H = G.copy(as_view=False) - >>> H = nx.Graph(G) - >>> H = G.fresh_copy().__class__(G) - - Fresh Data -- For fresh data, the graph structure is copied while - new empty data attribute dicts are created. The resulting graph - is independent of the original and it has no edge, node or graph - attributes. Fresh copies are not enabled. Instead use: - - >>> H = G.fresh_copy() - >>> H.add_nodes_from(G) - >>> H.add_edges_from(G.edges) - - View -- Inspired by dict-views, graph-views act like read-only - versions of the original graph, providing a copy of the original - structure without requiring any memory for copying the information. - - See the Python copy module for more information on shallow - and deep copies, https://docs.python.org/2/library/copy.html. - - Parameters - ---------- - as_view : bool, optional (default=False) - If True, the returned graph-view provides a read-only view - of the original graph without actually copying any data. - - Returns - ------- - G : Graph - A copy of the graph. - - See Also - -------- - to_directed: return a directed copy of the graph. - - Examples - -------- - >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc - >>> H = G.copy() - - """ - if as_view is True: - return nx.graphviews.DiGraphView(self) - G = self.fresh_copy() - G.graph.update(self.graph) - G.add_nodes_from((n, d.copy()) for n, d in self._node.items()) - G.add_edges_from((u, v, datadict.copy()) - for u, nbrs in self._adj.items() - for v, datadict in nbrs.items()) - return G - def to_undirected(self, reciprocal=False, as_view=False): """Return an undirected representation of the digraph. @@ -1195,8 +1108,9 @@ class DiGraph(Graph): >>> list(G2.edges) [(0, 1)] """ + graph_class = self.to_undirected_class() if as_view is True: - return nx.graphviews.GraphView(self) + return nx.graphviews.generic_graph_view(self, Graph) # deepcopy when not a view G = Graph() G.graph.update(deepcopy(self.graph)) @@ -1212,70 +1126,6 @@ class DiGraph(Graph): for v, d in nbrs.items()) return G - def subgraph(self, nodes): - """Return a SubGraph view of the subgraph induced on `nodes`. - - The induced subgraph of the graph contains the nodes in `nodes` - and the edges between those nodes. - - Parameters - ---------- - nodes : list, iterable - A container of nodes which will be iterated through once. - - Returns - ------- - G : SubGraph View - A subgraph view of the graph. The graph structure cannot be - changed but node/edge attributes can and are shared with the - original graph. - - Notes - ----- - The graph, edge and node attributes are shared with the original graph. - Changes to the graph structure is ruled out by the view, but changes - to attributes are reflected in the original graph. - - To create a subgraph with its own copy of the edge/node attributes use: - G.subgraph(nodes).copy() - - For an inplace reduction of a graph to a subgraph you can remove nodes: - G.remove_nodes_from([n for n in G if n not in set(nodes)]) - - Subgraph views are sometimes NOT what you want. In most cases where - you want to do more than simply look at the induced edges, it makes - more sense to just create the subgraph as its own graph with code like: - - :: - - # Create a subgraph SG based on a (possibly multigraph) G - SG = G.fresh_copy() - SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc) - if SG.is_multigraph: - SG.add_edges_from((n, nbr, key, d) - for n, nbrs in G.adj.items() if n in largest_wcc - for nbr, keydict in nbrs.items() if nbr in largest_wcc - for key, d in keydict.items()) - else: - SG.add_edges_from((n, nbr, d) - for n, nbrs in G.adj.items() if n in largest_wcc - for nbr, d in nbrs.items() if nbr in largest_wcc) - SG.graph.update(G.graph) - - Examples - -------- - >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc - >>> H = G.subgraph([0, 1, 2]) - >>> list(H.edges) - [(0, 1), (1, 2)] - """ - induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes)) - SubGraph = nx.graphviews.SubDiGraph - # if already a subgraph, don't make a chain - if hasattr(self, '_NODE_OK'): - return SubGraph(self._graph, induced_nodes, self._EDGE_OK) - return SubGraph(self, induced_nodes) - def reverse(self, copy=True): """Return the reverse of the graph. diff --git a/networkx/classes/function.py b/networkx/classes/function.py index 5997642c..06c0f278 100644 --- a/networkx/classes/function.py +++ b/networkx/classes/function.py @@ -183,6 +183,7 @@ def freeze(G): G.remove_nodes_from = frozen G.add_edge = frozen G.add_edges_from = frozen + G.add_weighted_edges_from = frozen G.remove_edge = frozen G.remove_edges_from = frozen G.clear = frozen @@ -362,13 +363,7 @@ def induced_subgraph(G, nbunch): [(0, 1), (1, 2)] """ induced_nodes = nx.filters.show_nodes(G.nbunch_iter(nbunch)) - if G.is_multigraph(): - if G.is_directed(): - return nx.graphviews.SubMultiDiGraph(G, induced_nodes) - return nx.graphviews.SubMultiGraph(G, induced_nodes) - if G.is_directed(): - return nx.graphviews.SubDiGraph(G, induced_nodes) - return nx.graphviews.SubGraph(G, induced_nodes) + return nx.graphviews.subgraph_view(G, induced_nodes) def edge_subgraph(G, edges): @@ -413,7 +408,6 @@ def edge_subgraph(G, edges): [(0, 1), (3, 4)] """ nxf = nx.filters - nxg = nx.graphviews edges = set(edges) nodes = set() for e in edges: @@ -422,14 +416,14 @@ def edge_subgraph(G, edges): if G.is_multigraph(): if G.is_directed(): induced_edges = nxf.show_multidiedges(edges) - return nxg.SubMultiDiGraph(G, induced_nodes, induced_edges) - induced_edges = nxf.show_multiedges(edges) - return nxg.SubMultiGraph(G, induced_nodes, induced_edges) - if G.is_directed(): - induced_edges = nxf.show_diedges(edges) - return nxg.SubDiGraph(G, induced_nodes, induced_edges) - induced_edges = nxf.show_edges(edges) - return nxg.SubGraph(G, induced_nodes, induced_edges) + else: + induced_edges = nxf.show_multiedges(edges) + else: + if G.is_directed(): + induced_edges = nxf.show_diedges(edges) + else: + induced_edges = nxf.show_edges(edges) + return nx.graphviews.subgraph_view(G, induced_nodes, induced_edges) def restricted_view(G, nodes, edges): @@ -475,19 +469,18 @@ def restricted_view(G, nodes, edges): [(2, 3)] """ nxf = nx.filters - nxg = nx.graphviews - h_nodes = nxf.hide_nodes(nodes) + hide_nodes = nxf.hide_nodes(nodes) if G.is_multigraph(): if G.is_directed(): - h_edges = nxf.hide_multidiedges(edges) - return nxg.SubMultiDiGraph(G, h_nodes, h_edges) - h_edges = nxf.hide_multiedges(edges) - return nxg.SubMultiGraph(G, h_nodes, h_edges) - if G.is_directed(): - h_edges = nxf.hide_diedges(edges) - return nxg.SubDiGraph(G, h_nodes, h_edges) - h_edges = nxf.hide_edges(edges) - return nxg.SubGraph(G, h_nodes, h_edges) + hide_edges = nxf.hide_multidiedges(edges) + else: + hide_edges = nxf.hide_multiedges(edges) + else: + if G.is_directed(): + hide_edges = nxf.hide_diedges(edges) + else: + hide_edges = nxf.hide_edges(edges) + return nx.graphviews.subgraph_view(G, hide_nodes, hide_edges) @not_implemented_for('undirected') @@ -496,9 +489,7 @@ def reverse_view(digraph): Identical to digraph.reverse(copy=False) """ - if digraph.is_multigraph(): - return nx.graphviews.MultiReverseView(digraph) - return nx.graphviews.ReverseView(digraph) + return nx.graphviews.reverse_view(digraph) def to_directed(graph): @@ -508,9 +499,7 @@ def to_directed(graph): Note that graph.to_directed defaults to `as_view=False` while this function always provides a view. """ - if graph.is_multigraph(): - return nx.graphviews.MultiDiGraphView(graph) - return nx.graphviews.DiGraphView(graph) + return graph.to_directed() def to_undirected(graph): @@ -520,9 +509,7 @@ def to_undirected(graph): Note that graph.to_undirected defaults to `as_view=False` while this function always provides a view. """ - if graph.is_multigraph(): - return nx.graphviews.MultiGraphView(graph) - return nx.graphviews.GraphView(graph) + return graph.to_undirected(as_view=True) def create_empty_copy(G, with_data=True): diff --git a/networkx/classes/graph.py b/networkx/classes/graph.py index 3ae0d369..c2d190f2 100644 --- a/networkx/classes/graph.py +++ b/networkx/classes/graph.py @@ -227,6 +227,20 @@ class Graph(object): dict which holds attrbute values keyed by attribute name. It should require no arguments and return a dict-like object. + Typically, if your extension doesn't impact the data structure all + methods will inherit without issue except: `to_directed/to_undirected`. + By default these methods create a DiGraph/Graph class and you probably + want them to create your extension of a DiGraph/Graph. To facilitate + this we define two class variables that you can set in your subclass. + + to_directed_class : callable, (default: DiGraph or MultiDiGraph) + Class to create a new graph structure in the `to_directed` method. + If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used. + + to_undirected_class : callable, (default: Graph or MultiGraph) + Class to create a new graph structure in the `to_undirected` method. + If `None`, a NetworkX class (Graph or MultiGraph) is used. + Examples -------- @@ -256,6 +270,22 @@ class Graph(object): adjlist_inner_dict_factory = dict edge_attr_dict_factory = dict + def to_directed_class(self): + """Returns the class to use for empty directed copies. + + If you subclass the base classes, use this to designate + what directed class to use for `to_directed()` copies. + """ + return nx.DiGraph + + def to_undirected_class(self): + """Returns the class to use for empty undirected copies. + + If you subclass the base classes, use this to designate + what directed class to use for `to_directed()` copies. + """ + return Graph + def __init__(self, incoming_graph_data=None, **attr): """Initialize a graph with edges, name, or graph attributes. @@ -1411,18 +1441,12 @@ class Graph(object): return False def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - - Notes - ----- - If you subclass the base class you should overwrite this method - to return your class of graph. + """Deprecated method to create an empty copy. Use __class__(). """ - return Graph() + # remove by v3 if not before + msg = 'G.fresh_copy is deprecated. Use G.__class__ instead' + warnings.warn(msg, DeprecationWarning, stacklevel=2) + return self.__class__() def copy(self, as_view=False): """Return a copy of the graph. @@ -1462,14 +1486,14 @@ class Graph(object): >>> H = G.copy() >>> H = G.copy(as_view=False) >>> H = nx.Graph(G) - >>> H = G.fresh_copy().__class__(G) + >>> H = G.__class__(G) Fresh Data -- For fresh data, the graph structure is copied while new empty data attribute dicts are created. The resulting graph is independent of the original and it has no edge, node or graph attributes. Fresh copies are not enabled. Instead use: - >>> H = G.fresh_copy() + >>> H = G.__class__() >>> H.add_nodes_from(G) >>> H.add_edges_from(G.edges) @@ -1502,8 +1526,8 @@ class Graph(object): """ if as_view is True: - return nx.graphviews.GraphView(self) - G = self.fresh_copy() + return nx.graphviews.generic_graph_view(self) + G = self.__class__() G.graph.update(self.graph) G.add_nodes_from((n, d.copy()) for n, d in self._node.items()) G.add_edges_from((u, v, datadict.copy()) @@ -1553,11 +1577,11 @@ class Graph(object): >>> list(H.edges) [(0, 1)] """ + graph_class = self.to_directed_class() if as_view is True: - return nx.graphviews.DiGraphView(self) + return nx.graphviews.generic_graph_view(self, graph_class) # deepcopy when not a view - from networkx import DiGraph - G = DiGraph() + G = graph_class() G.graph.update(deepcopy(self.graph)) G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) G.add_edges_from((u, v, deepcopy(data)) @@ -1608,10 +1632,11 @@ class Graph(object): >>> list(G2.edges) [(0, 1)] """ + graph_class = self.to_undirected_class() if as_view is True: - return nx.graphviews.GraphView(self) + return nx.graphviews.generic_graph_view(self, graph_class) # deepcopy when not a view - G = Graph() + G = graph_class() G.graph.update(deepcopy(self.graph)) G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) G.add_edges_from((u, v, deepcopy(d)) @@ -1656,7 +1681,7 @@ class Graph(object): :: # Create a subgraph SG based on a (possibly multigraph) G - SG = G.fresh_copy() + SG = G.__class__() SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc) if SG.is_multigraph: SG.add_edges_from((n, nbr, key, d) @@ -1677,11 +1702,11 @@ class Graph(object): [(0, 1), (1, 2)] """ induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes)) - SubGraph = nx.graphviews.SubGraph # if already a subgraph, don't make a chain + subgraph = nx.graphviews.subgraph_view if hasattr(self, '_NODE_OK'): - return SubGraph(self._graph, induced_nodes, self._EDGE_OK) - return SubGraph(self, induced_nodes) + return subgraph(self._graph, induced_nodes, self._EDGE_OK) + return subgraph(self, induced_nodes) def edge_subgraph(self, edges): """Returns the subgraph induced by the specified edges. diff --git a/networkx/classes/graphviews.py b/networkx/classes/graphviews.py index 2640eed5..f7b58427 100644 --- a/networkx/classes/graphviews.py +++ b/networkx/classes/graphviews.py @@ -13,20 +13,13 @@ In some algorithms it is convenient to temporarily morph a graph to exclude some nodes or edges. It should be better to do that via a view than to remove and then re-add. - In other algorithms it is convenient to temporarily morph a graph to reverse directed edges, or treat a directed graph as undirected, etc. This module provides those graph views. The resulting views are essentially read-only graphs that -report data from the orignal graph object. We provide three -attributes related to the underlying graph object. - - G._graph : the parent graph used for looking up graph data. - G.fresh_copy() : a method to return a null copy of the graph - represented by the view. This is useful if you want to - create a graph with the same data structure (directed/multi) - as the current view. +report data from the orignal graph object. We provide an +attribute G._graph which points to the underlying graph object. Note: Since graphviews look like graphs, one can end up with view-of-view-of-view chains. Be careful with chains because @@ -40,171 +33,141 @@ the chain is tricky and much harder with restricted_views than with induced subgraphs. Often it is easiest to use `.copy()` to avoid chains. """ -from collections import Mapping - -from networkx.classes import Graph, DiGraph, MultiGraph, MultiDiGraph -from networkx.classes.coreviews import ReadOnlyGraph, \ - AtlasView, AdjacencyView, MultiAdjacencyView, \ - FilterAtlas, FilterAdjacency, FilterMultiAdjacency, \ - UnionAdjacency, UnionMultiAdjacency -from networkx.classes.filters import no_filter, show_nodes, show_edges +from networkx.classes.coreviews import UnionAdjacency, UnionMultiAdjacency, \ + FilterAtlas, FilterAdjacency, FilterMultiAdjacency +from networkx.classes.filters import no_filter from networkx.exception import NetworkXError, NetworkXNotImplemented -from networkx.utils import not_implemented_for +# remove the graph class import when deprecated GraphView removed +from networkx.classes import Graph, DiGraph, MultiGraph, MultiDiGraph +import networkx as nx -__all__ = ['SubGraph', 'SubDiGraph', 'SubMultiGraph', 'SubMultiDiGraph', +__all__ = ['generic_graph_view', 'subgraph_view', 'reverse_view', + 'SubGraph', 'SubDiGraph', 'SubMultiGraph', 'SubMultiDiGraph', 'ReverseView', 'MultiReverseView', 'DiGraphView', 'MultiDiGraphView', 'GraphView', 'MultiGraphView', ] -class SubGraph(ReadOnlyGraph, Graph): - def __init__(self, graph, filter_node=no_filter, filter_edge=no_filter): - self._graph = graph - self._NODE_OK = filter_node - self._EDGE_OK = filter_edge - - # Set graph interface - self.graph = graph.graph - self._node = FilterAtlas(graph._node, filter_node) - self._adj = FilterAdjacency(graph._adj, filter_node, filter_edge) - - -class SubDiGraph(ReadOnlyGraph, DiGraph): - def __init__(self, graph, filter_node=no_filter, filter_edge=no_filter): - self._graph = graph - self._NODE_OK = filter_node - self._EDGE_OK = filter_edge - - # Set graph interface - self.graph = graph.graph - self._node = FilterAtlas(graph._node, filter_node) - self._adj = FilterAdjacency(graph._adj, filter_node, filter_edge) - self._pred = FilterAdjacency(graph._pred, filter_node, - lambda u, v: filter_edge(v, u)) - self._succ = self._adj - - -class SubMultiGraph(ReadOnlyGraph, MultiGraph): - def __init__(self, graph, filter_node=no_filter, filter_edge=no_filter): - self._graph = graph - self._NODE_OK = filter_node - self._EDGE_OK = filter_edge - - # Set graph interface - self.graph = graph.graph - self._node = FilterAtlas(graph._node, filter_node) - self._adj = FilterMultiAdjacency(graph._adj, filter_node, filter_edge) - - -class SubMultiDiGraph(ReadOnlyGraph, MultiDiGraph): - def __init__(self, graph, filter_node=no_filter, filter_edge=no_filter): - self._graph = graph - self._NODE_OK = filter_node - self._EDGE_OK = filter_edge - - # Set graph interface - self.graph = graph.graph - self._node = FilterAtlas(graph._node, filter_node) - FMA = FilterMultiAdjacency - self._adj = FMA(graph._adj, filter_node, filter_edge) - self._pred = FMA(graph._pred, filter_node, - lambda u, v, k: filter_edge(v, u, k)) - self._succ = self._adj - - -class ReverseView(ReadOnlyGraph, DiGraph): - def __init__(self, graph): - if not graph.is_directed(): - msg = "not implemented for undirected type" - raise NetworkXNotImplemented(msg) - - self._graph = graph - # Set graph interface - self.graph = graph.graph - self._node = graph._node - self._adj = graph._pred - self._pred = graph._succ - self._succ = self._adj - - -class MultiReverseView(ReadOnlyGraph, MultiDiGraph): - def __init__(self, graph): - if not graph.is_directed(): - msg = "not implemented for undirected type" - raise NetworkXNotImplemented(msg) - - self._graph = graph - # Set graph interface - self.graph = graph.graph - self._node = graph._node - self._adj = graph._pred - self._pred = graph._succ - self._succ = self._adj - - -class DiGraphView(ReadOnlyGraph, DiGraph): - def __init__(self, graph): - if graph.is_multigraph(): - msg = 'Wrong View class. Use MultiDiGraphView.' - raise NetworkXError(msg) - self._graph = graph - self.graph = graph.graph - self._node = graph._node - if graph.is_directed(): - self._pred = graph._pred - self._succ = graph._succ +def generic_graph_view(G, create_using=None): + if create_using is None: + newG = G.__class__() + else: + newG = nx.empty_graph(0, create_using) + if G.is_multigraph() != newG.is_multigraph(): + raise NetworkXError("Multigraph for G must agree with create_using") + newG = nx.freeze(newG) + + # create view by assigning attributes from G + newG._graph = G + newG.graph = G.graph + + newG._node = G._node + if newG.is_directed(): + if G.is_directed(): + newG._succ = G._succ + newG._pred = G._pred + newG._adj = G._succ else: - self._pred = graph._adj - self._succ = graph._adj - self._adj = self._succ - - -class MultiDiGraphView(ReadOnlyGraph, MultiDiGraph): - def __init__(self, graph): - if not graph.is_multigraph(): - msg = 'Wrong View class. Use DiGraphView.' - raise NetworkXError(msg) - self._graph = graph - self.graph = graph.graph - self._node = graph._node - if graph.is_directed(): - self._pred = graph._pred - self._succ = graph._succ - else: - self._pred = graph._adj - self._succ = graph._adj - self._adj = self._succ - - -class GraphView(ReadOnlyGraph, Graph): - UnionAdj = UnionAdjacency - - def __init__(self, graph): - if graph.is_multigraph(): - msg = 'Wrong View class. Use MultiGraphView.' - raise NetworkXError(msg) - self._graph = graph - self.graph = graph.graph - self._node = graph._node - if graph.is_directed(): - self._adj = self.UnionAdj(graph._succ, graph._pred) - else: - self._adj = graph._adj - - -class MultiGraphView(ReadOnlyGraph, MultiGraph): - UnionAdj = UnionMultiAdjacency - - def __init__(self, graph): - if not graph.is_multigraph(): - msg = 'Wrong View class. Use GraphView.' - raise NetworkXError(msg) - self._graph = graph - self.graph = graph.graph - self._node = graph._node - if graph.is_directed(): - self._adj = self.UnionAdj(graph._succ, graph._pred) + newG._succ = G._adj + newG._pred = G._adj + newG._adj = G._adj + elif G.is_directed(): + if G.is_multigraph(): + newG._adj = UnionMultiAdjacency(G._succ, G._pred) else: - self._adj = graph._adj + newG._adj = UnionAdjacency(G._succ, G._pred) + else: + newG._adj = G._adj + return newG + + +def subgraph_view(G, filter_node=no_filter, filter_edge=no_filter): + newG = nx.freeze(G.__class__()) + newG._NODE_OK = filter_node + newG._EDGE_OK = filter_edge + + # create view by assigning attributes from G + newG._graph = G + newG.graph = G.graph + + newG._node = FilterAtlas(G._node, filter_node) + if G.is_multigraph(): + Adj = FilterMultiAdjacency + + def reverse_edge(u, v, k): return filter_edge(v, u, k) + else: + Adj = FilterAdjacency + + def reverse_edge(u, v): return filter_edge(v, u) + if G.is_directed(): + newG._succ = Adj(G._succ, filter_node, filter_edge) + newG._pred = Adj(G._pred, filter_node, reverse_edge) + newG._adj = newG._succ + else: + newG._adj = Adj(G._adj, filter_node, filter_edge) + return newG + + +def reverse_view(G): + if not G.is_directed(): + msg = "not implemented for undirected type" + raise NetworkXNotImplemented(msg) + newG = generic_graph_view(G) + newG._succ, newG._pred = G._pred, G._succ + newG._adj = newG._succ + return newG + + +# The remaining definitions are for backward compatibility with v2.0 and 2.1 +def ReverseView(G): + # remove by v3 if not before + import warnings + msg = 'ReverseView is deprecated. Use reverse_view instead' + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return reverse_view(G) + + +def SubGraph(G, filter_node=no_filter, filter_edge=no_filter): + # remove by v3 if not before + import warnings + msg = 'SubGraph is deprecated. Use subgraph_view instead' + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return subgraph_view(G, filter_node, filter_edge) + + +def GraphView(G): + # remove by v3 if not before + import warnings + msg = 'GraphView is deprecated. Use generic_graph_view instead' + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return generic_graph_view(G, Graph) + + +def DiGraphView(G): + # remove by v3 if not before + import warnings + msg = 'GraphView is deprecated. Use generic_graph_view instead' + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return generic_graph_view(G, DiGraph) + + +def MultiGraphView(G): + # remove by v3 if not before + import warnings + msg = 'GraphView is deprecated. Use generic_graph_view instead' + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return generic_graph_view(G, MultiGraph) + + +def MultiDiGraphView(G): + # remove by v3 if not before + import warnings + msg = 'GraphView is deprecated. Use generic_graph_view instead' + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return generic_graph_view(G, MultiDiGraph) + + +MultiReverseView = ReverseView +SubMultiGraph = SubMultiDiGraph = SubDiGraph = SubGraph diff --git a/networkx/classes/multidigraph.py b/networkx/classes/multidigraph.py index 329fef4f..c97869c1 100644 --- a/networkx/classes/multidigraph.py +++ b/networkx/classes/multidigraph.py @@ -233,6 +233,20 @@ class MultiDiGraph(MultiGraph, DiGraph): dict which holds attrbute values keyed by attribute name. It should require no arguments and return a dict-like object. + Typically, if your extension doesn't impact the data structure all + methods will inherited without issue except: `to_directed/to_undirected`. + By default these methods create a DiGraph/Graph class and you probably + want them to create your extension of a DiGraph/Graph. To facilitate + this we define two class variables that you can set in your subclass. + + to_directed_class : callable, (default: DiGraph or MultiDiGraph) + Class to create a new graph structure in the `to_directed` method. + If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used. + + to_undirected_class : callable, (default: Graph or MultiGraph) + Class to create a new graph structure in the `to_undirected` method. + If `None`, a NetworkX class (Graph or MultiGraph) is used. + Examples -------- @@ -752,108 +766,6 @@ class MultiDiGraph(MultiGraph, DiGraph): """Return True if graph is directed, False otherwise.""" return True - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - - Notes - ----- - If you subclass the base class you should overwrite this method - to return your class of graph. - """ - return MultiDiGraph() - - def copy(self, as_view=False): - """Return a copy of the graph. - - The copy method by default returns an independent shallow copy - of the graph and attributes. That is, if an attribute is a - container, that container is shared by the original an the copy. - Use Python's `copy.deepcopy` for new containers. - - If `as_view` is True then a view is returned instead of a copy. - - Notes - ----- - All copies reproduce the graph structure, but data attributes - may be handled in different ways. There are four types of copies - of a graph that people might want. - - Deepcopy -- A "deepcopy" copies the graph structure as well as - all data attributes and any objects they might contain. - The entire graph object is new so that changes in the copy - do not affect the original object. (see Python's copy.deepcopy) - - Data Reference (Shallow) -- For a shallow copy the graph structure - is copied but the edge, node and graph attribute dicts are - references to those in the original graph. This saves - time and memory but could cause confusion if you change an attribute - in one graph and it changes the attribute in the other. - NetworkX does not provide this level of shallow copy. - - Independent Shallow -- This copy creates new independent attribute - dicts and then does a shallow copy of the attributes. That is, any - attributes that are containers are shared between the new graph - and the original. This is exactly what `dict.copy()` provides. - You can obtain this style copy using: - - >>> G = nx.path_graph(5) - >>> H = G.copy() - >>> H = G.copy(as_view=False) - >>> H = nx.Graph(G) - >>> H = G.fresh_copy().__class__(G) - - Fresh Data -- For fresh data, the graph structure is copied while - new empty data attribute dicts are created. The resulting graph - is independent of the original and it has no edge, node or graph - attributes. Fresh copies are not enabled. Instead use: - - >>> H = G.fresh_copy() - >>> H.add_nodes_from(G) - >>> H.add_edges_from(G.edges) - - View -- Inspired by dict-views, graph-views act like read-only - versions of the original graph, providing a copy of the original - structure without requiring any memory for copying the information. - - See the Python copy module for more information on shallow - and deep copies, https://docs.python.org/2/library/copy.html. - - Parameters - ---------- - as_view : bool, optional (default=False) - If True, the returned graph-view provides a read-only view - of the original graph without actually copying any data. - - Returns - ------- - G : Graph - A copy of the graph. - - See Also - -------- - to_directed: return a directed copy of the graph. - - Examples - -------- - >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc - >>> H = G.copy() - - """ - if as_view is True: - return nx.graphviews.MultiDiGraphView(self) - G = self.fresh_copy() - G.graph.update(self.graph) - G.add_nodes_from((n, d.copy()) for n, d in self._node.items()) - G.add_edges_from((u, v, key, datadict.copy()) - for u, nbrs in self._adj.items() - for v, keydict in nbrs.items() - for key, datadict in keydict.items()) - return G - def to_undirected(self, reciprocal=False, as_view=False): """Return an undirected representation of the digraph. @@ -905,10 +817,11 @@ class MultiDiGraph(MultiGraph, DiGraph): >>> list(G2.edges) [(0, 1)] """ + graph_class = self.to_undirected_class() if as_view is True: - return nx.graphviews.MultiGraphView(self) + return nx.graphviews.generic_graph_view(self, graph_class) # deepcopy when not a view - G = MultiGraph() + G = graph_class() G.graph.update(deepcopy(self.graph)) G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) if reciprocal is True: @@ -924,70 +837,6 @@ class MultiDiGraph(MultiGraph, DiGraph): for key, data in keydict.items()) return G - def subgraph(self, nodes): - """Return a SubGraph view of the subgraph induced on nodes in `nodes`. - - The induced subgraph of the graph contains the nodes in `nodes` - and the edges between those nodes. - - Parameters - ---------- - nodes : list, iterable - A container of nodes which will be iterated through once. - - Returns - ------- - G : SubGraph View - A subgraph view of the graph. The graph structure cannot be - changed but node/edge attributes can and are shared with the - original graph. - - Notes - ----- - The graph, edge and node attributes are shared with the original graph. - Changes to the graph structure is ruled out by the view, but changes - to attributes are reflected in the original graph. - - To create a subgraph with its own copy of the edge/node attributes use: - G.subgraph(nodes).copy() - - For an inplace reduction of a graph to a subgraph you can remove nodes: - G.remove_nodes_from([n for n in G if n not in set(nodes)]) - - Subgraph views are sometimes NOT what you want. In most cases where - you want to do more than simply look at the induced edges, it makes - more sense to just create the subgraph as its own graph with code like: - - :: - - # Create a subgraph SG based on a (possibly multigraph) G - SG = G.fresh_copy() - SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc) - if SG.is_multigraph: - SG.add_edges_from((n, nbr, key, d) - for n, nbrs in G.adj.items() if n in largest_wcc - for nbr, keydict in nbrs.items() if nbr in largest_wcc - for key, d in keydict.items()) - else: - SG.add_edges_from((n, nbr, d) - for n, nbrs in G.adj.items() if n in largest_wcc - for nbr, d in nbrs.items() if nbr in largest_wcc) - SG.graph.update(G.graph) - - Examples - -------- - >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc - >>> H = G.subgraph([0, 1, 2]) - >>> list(H.edges) - [(0, 1), (1, 2)] - """ - induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes)) - SubGraph = nx.graphviews.SubMultiDiGraph - # if already a subgraph, don't make a chain - if hasattr(self, '_NODE_OK'): - return SubGraph(self._graph, induced_nodes, self._EDGE_OK) - return SubGraph(self, induced_nodes) - def reverse(self, copy=True): """Return the reverse of the graph. @@ -1002,10 +851,10 @@ class MultiDiGraph(MultiGraph, DiGraph): the original graph. """ if copy: - H = self.fresh_copy() + H = self.__class__() H.graph.update(deepcopy(self.graph)) H.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) H.add_edges_from((v, u, k, deepcopy(d)) for u, v, k, d in self.edges(keys=True, data=True)) return H - return nx.graphviews.MultiReverseView(self) + return nx.graphviews.reverse_view(self) diff --git a/networkx/classes/multigraph.py b/networkx/classes/multigraph.py index a5543f63..cfbce77d 100644 --- a/networkx/classes/multigraph.py +++ b/networkx/classes/multigraph.py @@ -231,6 +231,20 @@ class MultiGraph(Graph): dict which holds attrbute values keyed by attribute name. It should require no arguments and return a dict-like object. + Typically, if your extension doesn't impact the data structure all + methods will inherited without issue except: `to_directed/to_undirected`. + By default these methods create a DiGraph/Graph class and you probably + want them to create your extension of a DiGraph/Graph. To facilitate + this we define two class variables that you can set in your subclass. + + to_directed_class : callable, (default: DiGraph or MultiDiGraph) + Class to create a new graph structure in the `to_directed` method. + If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used. + + to_undirected_class : callable, (default: Graph or MultiGraph) + Class to create a new graph structure in the `to_undirected` method. + If `None`, a NetworkX class (Graph or MultiGraph) is used. + Examples -------- @@ -244,6 +258,22 @@ class MultiGraph(Graph): edge_key_dict_factory = dict # edge_attr_dict_factory = dict + def to_directed_class(self): + """Returns the class to use for empty directed copies. + + If you subclass the base classes, use this to designate + what directed class to use for `to_directed()` copies. + """ + return nx.MultiDiGraph + + def to_undirected_class(self): + """Returns the class to use for empty undirected copies. + + If you subclass the base classes, use this to designate + what directed class to use for `to_directed()` copies. + """ + return MultiGraph + def __init__(self, incoming_graph_data=None, **attr): """Initialize a graph with edges, name, or graph attributes. @@ -845,20 +875,6 @@ class MultiGraph(Graph): """Return True if graph is directed, False otherwise.""" return False - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - - Notes - ----- - If you subclass the base class you should overwrite this method - to return your class of graph. - """ - return MultiGraph() - def copy(self, as_view=False): """Return a copy of the graph. @@ -897,14 +913,14 @@ class MultiGraph(Graph): >>> H = G.copy() >>> H = G.copy(as_view=False) >>> H = nx.Graph(G) - >>> H = G.fresh_copy().__class__(G) + >>> H = G.__class__(G) Fresh Data -- For fresh data, the graph structure is copied while new empty data attribute dicts are created. The resulting graph is independent of the original and it has no edge, node or graph attributes. Fresh copies are not enabled. Instead use: - >>> H = G.fresh_copy() + >>> H = G.__class__() >>> H.add_nodes_from(G) >>> H.add_edges_from(G.edges) @@ -937,8 +953,8 @@ class MultiGraph(Graph): """ if as_view is True: - return nx.graphviews.MultiGraphView(self) - G = self.fresh_copy() + return nx.graphviews.generic_graph_view(self) + G = self.__class__() G.graph.update(self.graph) G.add_nodes_from((n, d.copy()) for n, d in self._node.items()) G.add_edges_from((u, v, key, datadict.copy()) @@ -989,11 +1005,11 @@ class MultiGraph(Graph): >>> list(H.edges) [(0, 1)] """ + graph_class = self.to_directed_class() if as_view is True: - return nx.graphviews.MultiDiGraphView(self) + return nx.graphviews.generic_graph_view(self, graph_class) # deepcopy when not a view - from networkx.classes.multidigraph import MultiDiGraph - G = MultiDiGraph() + G = graph_class() G.graph.update(deepcopy(self.graph)) G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) G.add_edges_from((u, v, key, deepcopy(datadict)) @@ -1040,10 +1056,11 @@ class MultiGraph(Graph): >>> list(G2.edges) [(0, 1)] """ + graph_class = self.to_undirected_class() if as_view is True: - return nx.graphviews.MultiGraphView(self) + return nx.graphviews.generic_graph_view(self, graph_class) # deepcopy when not a view - G = MultiGraph() + G = graph_class() G.graph.update(deepcopy(self.graph)) G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items()) G.add_edges_from((u, v, key, deepcopy(datadict)) @@ -1052,71 +1069,6 @@ class MultiGraph(Graph): for key, datadict in keydict.items()) return G - def subgraph(self, nodes): - """Return a SubGraph view of the subgraph induced on nodes in `nodes`. - - The induced subgraph of the graph contains the nodes in `nodes` - and the edges between those nodes. - - Parameters - ---------- - nodes : list, iterable - A container of nodes which will be iterated through once. - - Returns - ------- - G : SubGraph View - A subgraph view of the graph. The graph structure cannot be - changed but node/edge attributes can and are shared with the - original graph. - - Notes - ----- - The graph, edge and node attributes are shared with the original graph. - Changes to the graph structure is ruled out by the view, but changes - to attributes are reflected in the original graph. - - To create a subgraph with its own copy of the edge/node attributes use: - G.subgraph(nodes).copy() - - For an inplace reduction of a graph to a subgraph you can remove nodes: - G.remove_nodes_from([n for n in G if n not in set(nodes)]) - - Subgraph views are sometimes NOT what you want. In most cases where - you want to do more than simply look at the induced edges, it makes - more sense to just create the subgraph as its own graph with code like: - - :: - - # Create a subgraph SG based on a (possibly multigraph) G - SG = G.fresh_copy() - SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc) - if SG.is_multigraph: - SG.add_edges_from((n, nbr, key, d) - for n, nbrs in G.adj.items() if n in largest_wcc - for nbr, keydict in nbrs.items() if nbr in largest_wcc - for key, d in keydict.items()) - else: - SG.add_edges_from((n, nbr, d) - for n, nbrs in G.adj.items() if n in largest_wcc - for nbr, d in nbrs.items() if nbr in largest_wcc) - SG.graph.update(G.graph) - - Examples - -------- - >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc - >>> nx.add_path(G, [0, 1, 2, 3]) - >>> H = G.subgraph([0, 1, 2]) - >>> list(H.edges) - [(0, 1), (1, 2)] - """ - induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes)) - SubGraph = nx.graphviews.SubMultiGraph - # if already a subgraph, don't make a chain - if hasattr(self, '_NODE_OK'): - return SubGraph(self._graph, induced_nodes, self._EDGE_OK) - return SubGraph(self, induced_nodes) - def number_of_edges(self, u=None, v=None): """Return the number of edges between two nodes. diff --git a/networkx/classes/ordered.py b/networkx/classes/ordered.py index 89e55316..a6eedabe 100644 --- a/networkx/classes/ordered.py +++ b/networkx/classes/ordered.py @@ -1,7 +1,7 @@ """ Consistently ordered variants of the default base classes. -Note that if you are using Python 3.6, you shouldn't need these classes -because the dicts in Python 3.6 are ordered. +Note that if you are using Python 3.6+, you shouldn't need these classes +because the dicts in Python 3.6+ are ordered. Note also that there are many differing expectations for the word "ordered" and that these classes may not provide the order you expect. The intent here is to give a consistent order not a particular order. @@ -49,15 +49,6 @@ class OrderedGraph(Graph): adjlist_inner_dict_factory = OrderedDict edge_attr_dict_factory = OrderedDict - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - """ - return OrderedGraph() - class OrderedDiGraph(DiGraph): """Consistently ordered variant of :class:`~networkx.DiGraph`.""" @@ -66,15 +57,6 @@ class OrderedDiGraph(DiGraph): adjlist_inner_dict_factory = OrderedDict edge_attr_dict_factory = OrderedDict - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - """ - return OrderedDiGraph() - class OrderedMultiGraph(MultiGraph): """Consistently ordered variant of :class:`~networkx.MultiGraph`.""" @@ -84,15 +66,6 @@ class OrderedMultiGraph(MultiGraph): edge_key_dict_factory = OrderedDict edge_attr_dict_factory = OrderedDict - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - """ - return OrderedMultiGraph() - class OrderedMultiDiGraph(MultiDiGraph): """Consistently ordered variant of :class:`~networkx.MultiDiGraph`.""" @@ -101,12 +74,3 @@ class OrderedMultiDiGraph(MultiDiGraph): adjlist_inner_dict_factory = OrderedDict edge_key_dict_factory = OrderedDict edge_attr_dict_factory = OrderedDict - - def fresh_copy(self): - """Return a fresh copy graph with the same data structure. - - A fresh copy has no nodes, edges or graph attributes. It is - the same data structure as the current graph. This method is - typically used to create an empty version of the graph. - """ - return OrderedMultiDiGraph() diff --git a/networkx/classes/tests/test_graphviews.py b/networkx/classes/tests/test_graphviews.py index cd82e89b..da37d0bc 100644 --- a/networkx/classes/tests/test_graphviews.py +++ b/networkx/classes/tests/test_graphviews.py @@ -33,7 +33,24 @@ class TestReverseView(object): def test_exceptions(self): nxg = nx.graphviews - assert_raises(nx.NetworkXNotImplemented, nxg.ReverseView, nx.Graph()) + assert_raises(nx.NetworkXNotImplemented, nxg.reverse_view, nx.Graph()) + + def test_subclass(self): + class MyGraph(nx.DiGraph): + def my_method(self): + return "me" + def to_directed_class(self): + return MyGraph() + + M = MyGraph() + M.add_edge(1, 2) + RM = nx.reverse_view(M) + print("RM class",RM.__class__) + RMC = RM.copy() + print("RMC class",RMC.__class__) + print(RMC.edges) + assert_true(RMC.has_edge(2, 1)) + assert_equal(RMC.my_method(), "me") class TestMultiReverseView(object): @@ -65,7 +82,7 @@ class TestMultiReverseView(object): def test_exceptions(self): nxg = nx.graphviews MG = nx.MultiGraph(self.G) - assert_raises(nx.NetworkXNotImplemented, nxg.MultiReverseView, MG) + assert_raises(nx.NetworkXNotImplemented, nxg.reverse_view, MG) class TestToDirected(object): @@ -221,6 +238,13 @@ class TestChainsOfViews(object): assert_is_not(RSG._graph, self.G) assert_edges_equal(RSG.edges, CG.edges) + def test_subgraph_copy(self): + for origG in self.graphs: + G = nx.OrderedGraph(origG) + SG = G.subgraph([4, 5, 6]) + H = SG.copy() + assert_equal(type(G), type(H)) + def test_subgraph_todirected(self): SG = nx.induced_subgraph(self.G, [4, 5, 6]) SSG = SG.to_directed() @@ -263,29 +287,50 @@ class TestChainsOfViews(object): SG = G.subgraph([4, 5, 6]) CSG = SG.copy(as_view=True) DCSG = SG.copy(as_view=False) - assert_equal(CSG.__class__.__name__, 'GraphView') - assert_equal(DCSG.__class__.__name__, 'Graph') + assert_true(hasattr(CSG, '_graph')) # is a view + assert_false(hasattr(DCSG, '_graph')) # not a view def test_copy_disubgraph(self): G = self.DG.copy() SG = G.subgraph([4, 5, 6]) CSG = SG.copy(as_view=True) DCSG = SG.copy(as_view=False) - assert_equal(CSG.__class__.__name__, 'DiGraphView') - assert_equal(DCSG.__class__.__name__, 'DiGraph') + assert_true(hasattr(CSG, '_graph')) # is a view + assert_false(hasattr(DCSG, '_graph')) # not a view def test_copy_multidisubgraph(self): G = self.MDG.copy() SG = G.subgraph([4, 5, 6]) CSG = SG.copy(as_view=True) DCSG = SG.copy(as_view=False) - assert_equal(CSG.__class__.__name__, 'MultiDiGraphView') - assert_equal(DCSG.__class__.__name__, 'MultiDiGraph') + assert_true(hasattr(CSG, '_graph')) # is a view + assert_false(hasattr(DCSG, '_graph')) # not a view def test_copy_multisubgraph(self): - G = self.MGv.copy() + G = self.MG.copy() SG = G.subgraph([4, 5, 6]) CSG = SG.copy(as_view=True) DCSG = SG.copy(as_view=False) - assert_equal(CSG.__class__.__name__, 'MultiGraphView') - assert_equal(DCSG.__class__.__name__, 'MultiGraph') + assert_true(hasattr(CSG, '_graph')) # is a view + assert_false(hasattr(DCSG, '_graph')) # not a view + + def test_copy_of_view(self): + G = nx.OrderedMultiGraph(self.MGv) + assert_equal(G.__class__.__name__, 'OrderedMultiGraph') + G = G.copy(as_view=True) + assert_equal(G.__class__.__name__, 'OrderedMultiGraph') + + def test_subclass(self): + class MyGraph(nx.DiGraph): + def my_method(self): + return "me" + def to_directed_class(self): + return MyGraph() + + for origG in self.graphs: + G = MyGraph(origG) + SG = G.subgraph([4, 5, 6]) + H = SG.copy() + assert_equal(SG.my_method(), "me") + assert_equal(H.my_method(), "me") + assert_false(3 in H or 3 in SG) diff --git a/networkx/classes/tests/test_subgraphviews.py b/networkx/classes/tests/test_subgraphviews.py index d63f59b8..c3a7dbcf 100644 --- a/networkx/classes/tests/test_subgraphviews.py +++ b/networkx/classes/tests/test_subgraphviews.py @@ -5,7 +5,7 @@ import networkx as nx class TestSubGraphView(object): - gview = nx.graphviews.SubGraph + gview = staticmethod(nx.graphviews.SubGraph) graph = nx.Graph hide_edges_filter = staticmethod(nx.filters.hide_edges) show_edges_filter = staticmethod(nx.filters.show_edges) @@ -17,7 +17,9 @@ class TestSubGraphView(object): def test_hidden_nodes(self): hide_nodes = [4, 5, 111] nodes_gone = nx.filters.hide_nodes(hide_nodes) - G = self.gview(self.G, filter_node=nodes_gone) + gview = self.gview + print(gview) + G = gview(self.G, filter_node=nodes_gone) assert_equal(self.G.nodes - G.nodes, {4, 5}) assert_equal(self.G.edges - G.edges, self.hide_edges_w_hide_nodes) if G.is_directed(): @@ -35,7 +37,8 @@ class TestSubGraphView(object): def test_hidden_edges(self): hide_edges = [(2, 3), (8, 7), (222, 223)] edges_gone = self.hide_edges_filter(hide_edges) - G = self.gview(self.G, filter_edge=edges_gone) + gview = self.gview + G = gview(self.G, filter_edge=edges_gone) assert_equal(self.G.nodes, G.nodes) if G.is_directed(): assert_equal(self.G.edges - G.edges, {(2, 3)}) @@ -54,7 +57,8 @@ class TestSubGraphView(object): def test_shown_node(self): induced_subgraph = nx.filters.show_nodes([2, 3, 111]) - G = self.gview(self.G, filter_node=induced_subgraph) + gview = self.gview + G = gview(self.G, filter_node=induced_subgraph) assert_equal(set(G.nodes), {2, 3}) if G.is_directed(): assert_equal(list(G[3]), []) @@ -90,7 +94,7 @@ class TestSubGraphView(object): class TestSubDiGraphView(TestSubGraphView): - gview = nx.graphviews.SubDiGraph + gview = staticmethod(nx.graphviews.SubDiGraph) graph = nx.DiGraph hide_edges_filter = staticmethod(nx.filters.hide_diedges) show_edges_filter = staticmethod(nx.filters.show_diedges) @@ -129,7 +133,7 @@ class TestSubDiGraphView(TestSubGraphView): # multigraph class TestMultiGraphView(TestSubGraphView): - gview = nx.graphviews.SubMultiGraph + gview = staticmethod(nx.graphviews.SubMultiGraph) graph = nx.MultiGraph hide_edges_filter = staticmethod(nx.filters.hide_multiedges) show_edges_filter = staticmethod(nx.filters.show_multiedges) @@ -184,7 +188,7 @@ class TestMultiGraphView(TestSubGraphView): # multidigraph class TestMultiDiGraphView(TestMultiGraphView, TestSubDiGraphView): - gview = nx.graphviews.SubMultiDiGraph + gview = staticmethod(nx.graphviews.SubMultiDiGraph) graph = nx.MultiDiGraph hide_edges_filter = staticmethod(nx.filters.hide_multidiedges) show_edges_filter = staticmethod(nx.filters.show_multidiedges) |
