Repository: networkx/networkx

Issue:
Behavior of `nx.union_all` when `graphs=[]`
I'm running into a case where I'm passing in an empty list to `nx.union_all`, and it returns a None type. 

While this is not necessarily the wrong thing to do, it is not documented.

Intuitively, I would expect the result of union with no inputs to be an empty graph, but the issue here is that you don't know what type the graph should be. Therefore I think the best behavior would be to raise a ValueError indicating that the input cannot be empty. This would make it more clear where the code is failing. 

Current behavior:

```python

>>> nx.union_all([nx.path_graph([1, 2])])
<networkx.classes.graph.Graph at 0x7f6fb15d1ac8>

>>> nx.union_all([nx.path_graph([1, 2]), nx.path_graph([3, 4])])
<networkx.classes.graph.Graph at 0x7f6fb1477ac8>

>>> print(nx.union_all([]))
None

```

Proposed Behavior:

```python
>>> print(nx.union_all([]))
ValueError: Cannot union_all an empty list
```
Behavior of `nx.union_all` when `graphs=[]`
I'm running into a case where I'm passing in an empty list to `nx.union_all`, and it returns a None type. 

While this is not necessarily the wrong thing to do, it is not documented.

Intuitively, I would expect the result of union with no inputs to be an empty graph, but the issue here is that you don't know what type the graph should be. Therefore I think the best behavior would be to raise a ValueError indicating that the input cannot be empty. This would make it more clear where the code is failing. 

Current behavior:

```python

>>> nx.union_all([nx.path_graph([1, 2])])
<networkx.classes.graph.Graph at 0x7f6fb15d1ac8>

>>> nx.union_all([nx.path_graph([1, 2]), nx.path_graph([3, 4])])
<networkx.classes.graph.Graph at 0x7f6fb1477ac8>

>>> print(nx.union_all([]))
None

```

Proposed Behavior:

```python
>>> print(nx.union_all([]))
ValueError: Cannot union_all an empty list
```


Discussion:
This seems like a good way to handle an empty incoming list.  Thanks!
This seems like a good way to handle an empty incoming list.  Thanks!

Resolving patch:
diff --git a/networkx/algorithms/operators/all.py b/networkx/algorithms/operators/all.py
--- a/networkx/algorithms/operators/all.py
+++ b/networkx/algorithms/operators/all.py
@@ -38,6 +38,11 @@ def union_all(graphs, rename=(None,)):
     -------
     U : a graph with the same type as the first graph in list
 
+    Raises
+    ------
+    ValueError
+       If `graphs` is an empty list.
+
     Notes
     -----
     To force a disjoint union with node relabeling, use
@@ -52,6 +57,8 @@ def union_all(graphs, rename=(None,)):
     union
     disjoint_union_all
     """
+    if not graphs:
+        raise ValueError('cannot apply union_all to an empty list')
     graphs_names = zip_longest(graphs, rename)
     U, gname = next(graphs_names)
     for H, hname in graphs_names:
@@ -75,6 +82,11 @@ def disjoint_union_all(graphs):
     -------
     U : A graph with the same type as the first graph in list
 
+    Raises
+    ------
+    ValueError
+       If `graphs` is an empty list.
+
     Notes
     -----
     It is recommended that the graphs be either all directed or all undirected.
@@ -83,6 +95,8 @@ def disjoint_union_all(graphs):
     If a graph attribute is present in multiple graphs, then the value
     from the last graph in the list with that attribute is used.
     """
+    if not graphs:
+        raise ValueError('cannot apply disjoint_union_all to an empty list')
     graphs = iter(graphs)
     U = next(graphs)
     for H in graphs:
@@ -105,6 +119,11 @@ def compose_all(graphs):
     -------
     C : A graph with the same type as the first graph in list
 
+    Raises
+    ------
+    ValueError
+       If `graphs` is an empty list.
+
     Notes
     -----
     It is recommended that the supplied graphs be either all directed or all
@@ -114,6 +133,8 @@ def compose_all(graphs):
     If a graph attribute is present in multiple graphs, then the value
     from the last graph in the list with that attribute is used.
     """
+    if not graphs:
+        raise ValueError('cannot apply compose_all to an empty list')
     graphs = iter(graphs)
     C = next(graphs)
     for H in graphs:
@@ -136,11 +157,18 @@ def intersection_all(graphs):
     -------
     R : A new graph with the same type as the first graph in list
 
+    Raises
+    ------
+    ValueError
+       If `graphs` is an empty list.
+
     Notes
     -----
     Attributes from the graph, nodes, and edges are not copied to the new
     graph.
     """
+    if not graphs:
+        raise ValueError('cannot apply intersection_all to an empty list')
     graphs = iter(graphs)
     R = next(graphs)
     for H in graphs:


Test patch:
diff --git a/networkx/algorithms/operators/tests/test_all.py b/networkx/algorithms/operators/tests/test_all.py
--- a/networkx/algorithms/operators/tests/test_all.py
+++ b/networkx/algorithms/operators/tests/test_all.py
@@ -199,3 +199,23 @@ def test_mixed_type_compose():
     H = nx.MultiGraph()
     I = nx.Graph()
     U = nx.compose_all([G, H, I])
+
+
+@raises(ValueError)
+def test_empty_union():
+    nx.union_all([])
+
+
+@raises(ValueError)
+def test_empty_disjoint_union():
+    nx.disjoint_union_all([])
+
+
+@raises(ValueError)
+def test_empty_compose_all():
+    nx.compose_all([])
+
+
+@raises(ValueError)
+def test_empty_intersection_all():
+    nx.intersection_all([])