Repository: networkx/networkx

Issue:
instructions for removing self-edges in `configuration_model` are out of date
In the configuration model documentation, it says to use `G.remove_edges_from(nx.selfloop_edges(G))` to remove self-edges.  This no longer works (I think it broke with 2.0).  Something to do with the dictionary changing size while iterating.

The documentation should be updated to `G.remove_edges_from(list(nx.selfloop_edges(G)))`.

It's curious that this breaks, because it does seem to work on directed graphs.  That suggests that there's something I don't understand about the internal details of how the different graph types are implemented.


Discussion:
I am able to use it on a Graph without any issues. We'll need to drill down to how our configs differ.

```python
import networkx as nx
G = nx.path_graph(9)
G.add_edge(3,3)
list(nx.selfloop_edges(G))
#           [(3, 3)]
G.remove_edges_from(nx.selfloop_edges(G))
list(nx.selfloop_edges(G))
#           []
```
Python 3.7.6,   NetworkX 2.5rc1.dev_20200712223336

In quick testing it appears to consistently happen with MultiGraphs (which is what `configuration_model` returns).
Thanks for this!

With MultiGraphs, the (new with v2.0) iterator nature of ```nx.selfloops_edges``` caused this RuntimeError.

That's because the selfloop_edges iteration goes through the elements of the neighbor dict (e.g. G.adj[n]) and the neighbor dict gets changed. This should cause problems with MultiGraph and MultiDiGraph (I'm not sure why you aren't getting the trouble with MultiDiGraph).

Strangely, this doesn't happen with Graph and DiGraph because, while now the iteration goes through G.adj it does not go through G.adj[n].  So, removing a neighbor changes the value of G.adj but doesn't change the G.adj dict itself.

I would like to keep ```G.remove_edges_from(nx.selfloop_edges(G))``` working though...
Luckily, that case can be finessed because instead of iterating over the G.adj[n] dict, we can iterate over ```range(len(G.adj[n]))``` and still get the same number of selfloop edges to remove. Note that ```G.remove_edges_from(nx.selfloop_edges(G, keys=True))``` would not allow that since we actually do need the keys in this case. 

Here's some code for testing.  @joelmiller can you check that I haven't missed something with the MultiDiGraph case?
```python
# test removing selfloops behavior vis-a-vis altering a dict while iterating
G.add_edge(0, 0)
G.remove_edges_from(nx.selfloop_edges(G))
if G.is_multigraph():
    G.add_edge(0, 0)
    pytest.raises(RuntimeError, G.remove_edges_from, nx.selfloop_edges(G, keys=True))
    G.add_edge(0, 0)
    pytest.raises(TypeError, G.remove_edges_from, nx.selfloop_edges(G, data=True))
    G.add_edge(0, 0)
    pytest.raises(RuntimeError, G.remove_edges_from, nx.selfloop_edges(G, data=True, keys=True))
else:
    G.add_edge(0, 0)
    G.remove_edges_from(nx.selfloop_edges(G, keys=True))
    G.add_edge(0, 0)
    G.remove_edges_from(nx.selfloop_edges(G, data=True))
    G.add_edge(0, 0)
    G.remove_edges_from(nx.selfloop_edges(G, keys=True, data=True))
```


That looks right to me.  On testing, I do see the same error with MultiDiGraph.

No time at the moment to do an in-depth check (kids at home for Melbourne lockdown + modelling of infectious diseases = busy busy.).

Resolving patch:
diff --git a/networkx/classes/function.py b/networkx/classes/function.py
--- a/networkx/classes/function.py
+++ b/networkx/classes/function.py
@@ -1205,7 +1205,7 @@ def selfloop_edges(G, data=False, keys=False, default=None):
                     (n, n)
                     for n, nbrs in G.adj.items()
                     if n in nbrs
-                    for d in nbrs[n].values()
+                    for i in range(len(nbrs[n]))  # for easy edge removal (#4068)
                 )
         else:
             return ((n, n) for n, nbrs in G.adj.items() if n in nbrs)


Test patch:
diff --git a/networkx/classes/tests/test_function.py b/networkx/classes/tests/test_function.py
--- a/networkx/classes/tests/test_function.py
+++ b/networkx/classes/tests/test_function.py
@@ -664,3 +664,28 @@ def test_selfloops():
         assert_edges_equal(
             nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)]
         )
+        # test removing selfloops behavior vis-a-vis altering a dict while iterating
+        G.add_edge(0, 0)
+        G.remove_edges_from(nx.selfloop_edges(G))
+        if G.is_multigraph():
+            G.add_edge(0, 0)
+            pytest.raises(
+                RuntimeError, G.remove_edges_from, nx.selfloop_edges(G, keys=True)
+            )
+            G.add_edge(0, 0)
+            pytest.raises(
+                TypeError, G.remove_edges_from, nx.selfloop_edges(G, data=True)
+            )
+            G.add_edge(0, 0)
+            pytest.raises(
+                RuntimeError,
+                G.remove_edges_from,
+                nx.selfloop_edges(G, data=True, keys=True),
+            )
+        else:
+            G.add_edge(0, 0)
+            G.remove_edges_from(nx.selfloop_edges(G, keys=True))
+            G.add_edge(0, 0)
+            G.remove_edges_from(nx.selfloop_edges(G, data=True))
+            G.add_edge(0, 0)
+            G.remove_edges_from(nx.selfloop_edges(G, keys=True, data=True))