Repository: networkx/networkx

Issue:
louvain_partitions iteration difference
louvain_partitions leads to different clusterings depending on how one iterates over the returned generator

### Current Behavior
If I iterate over the generator returned from louvain_partitions by a for loop I get for each iteration the expected clustering levels. 
However, if I first create a list (or tuple, deque...) and then iterate over the list I get different results. In fact, the levels have certain clusters which are empty if I iterate over the list. 

### Expected Behavior
I would expect that no matter how I iterate over the resulting generator I would get the same results.

### Steps to Reproduce
```
import networkx as nx
import networkx.algorithms.community as nx_comm

G = nx.path_graph(200)

def evaluate(part):
    print(len(part))
    count = 0
    for nodes in part:
        if len(nodes) == 0:
            count += 1
    print(f"\tEmpty Clusters: {count}")

louvian_clustering = list(nx_comm.louvain_partitions(G, resolution=1, seed=20))
for part in louvian_clustering:
    evaluate(part)

print("----")

for part in nx_comm.louvain_partitions(G, resolution=1, seed=20):
    evaluate(part)
```
Output
```
90
    Empty Clusters: 76 
41
    Empty Clusters: 27 
17 
    Empty Clusters: 3
 14
    Empty Clusters: 0
 ----
 90
    Empty Clusters: 0
 41 
    Empty Clusters: 0 
17
    Empty Clusters: 0 
14 
    Empty Clusters: 0
```

### Environment
<!--- Please provide details about your local environment -->
Python version: 3.10.5
NetworkX version: 2.8.5



Discussion:
Thank you very much for reporting this!!

This is a very strange an ugly bug.  It stems from the function yielding a list-of-sets without copying those sets. When the next partition is requested, the sets are updated while computing the next partition. So the originally yielded partition gets changed while computing the next partition. This doesn't show up if you process each partition before iterating to the next partition. Here's a test that fails due to this bug.

```python
def test_partition_iterator():
    G = nx.path_graph(15)
    parts_iter = nx.community.louvain_partitions(G, seed=42)
    first_part = next(parts_iter)
    first_copy = [s.copy() for s in first_part]

    # gh-5901 reports sets changing after next partition is yielded
    assert first_copy[0] == first_part[0]
    second_part = next(parts_iter)
    assert first_copy[0] == first_part[0]
```

Resolving patch:
diff --git a/networkx/algorithms/community/louvain.py b/networkx/algorithms/community/louvain.py
--- a/networkx/algorithms/community/louvain.py
+++ b/networkx/algorithms/community/louvain.py
@@ -179,7 +179,8 @@ def louvain_partitions(
     )
     improvement = True
     while improvement:
-        yield partition
+        # gh-5901 protect the sets in the yielded list from further manipulation here
+        yield [s.copy() for s in partition]
         new_mod = modularity(
             graph, inner_partition, resolution=resolution, weight="weight"
         )


Test patch:
diff --git a/networkx/algorithms/community/tests/test_louvain.py b/networkx/algorithms/community/tests/test_louvain.py
--- a/networkx/algorithms/community/tests/test_louvain.py
+++ b/networkx/algorithms/community/tests/test_louvain.py
@@ -2,6 +2,7 @@
 from networkx.algorithms.community import (
     is_partition,
     louvain_communities,
+    louvain_partitions,
     modularity,
     partition_quality,
 )
@@ -30,7 +31,7 @@ def test_valid_partition():
     assert is_partition(H, partition2)
 
 
-def test_partition():
+def test_karate_club_partition():
     G = nx.karate_club_graph()
     part = [
         {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
@@ -43,6 +44,18 @@ def test_partition():
     assert part == partition
 
 
+def test_partition_iterator():
+    G = nx.path_graph(15)
+    parts_iter = louvain_partitions(G, seed=42)
+    first_part = next(parts_iter)
+    first_copy = [s.copy() for s in first_part]
+
+    # gh-5901 reports sets changing after next partition is yielded
+    assert first_copy[0] == first_part[0]
+    second_part = next(parts_iter)
+    assert first_copy[0] == first_part[0]
+
+
 def test_directed_partition():
     """
     Test 2 cases that were looping infinitely