Repository: pyvista/pyvista

Issue:
`MultiBlock` self-mutates during iteration and breaks `zip`
### Describe the bug, what's wrong, and what you expected.

`MulitBlock.__next__` stores an internal counter which self-mutates during iteration. This breaks things like `zip` when iterating over multiple copies of the same `MultiBlock` instance.

### Steps to reproduce the bug.


``` python
# Zip a MultiBlock with two blocks
import pyvista as pv
multi = pv.MultiBlock([pv.Sphere(), None])
multi2=multi
print(list(zip(multi, multi2)))

# Expected four items, but got two instead.
[(PolyData (0x130847ca0)
  N Cells:    1680
  N Points:   842
  N Strips:   0
  X Bounds:   -4.993e-01, 4.993e-01
  Y Bounds:   -4.965e-01, 4.965e-01
  Z Bounds:   -5.000e-01, 5.000e-01
  N Arrays:   1, None)]

# Compare with 4 items returned by `tuple` (as expected):
tup = (1,2)
tup2=tup
print(list(zip(tup, tup2)))
[(1, 1), (2, 2)]
```

### System Information

```shell
Dev branch
```


### Screenshots

_No response_

Discussion:
MatthewFlamm: Test was more or less directly copied from #6600 .  @user27182 I'm not sure how to give you credit for it in this PR.  One thought is you could make a suggested change (to the comment in the test for example) and commit it (or I commit it), then you would get added to the git history on the main branch.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pyvista/pyvista/pull/6644?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.06%. Comparing base [(`6647524`)](https://app.codecov.io/gh/pyvista/pyvista/commit/664752494d7ba82a1d79fae72c2a7e1515c95e7c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) to head [(`928682a`)](https://app.codecov.io/gh/pyvista/pyvista/commit/928682ac51e41f86eb7a4be6ce3b24c884a01284?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista).
> Report is 13 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6644      +/-   ##
==========================================
- Coverage   97.37%   97.06%   -0.32%     
==========================================
  Files         143      143              
  Lines       27786    27831      +45     
==========================================
- Hits        27058    27014      -44     
- Misses        728      817      +89     
```

</details>
MatthewFlamm: Looks like we also need to do this for PartionedDataSet https://github.com/pyvista/pyvista/blob/262e2d217f612b644442e5e14ccfa0ccd950b126/pyvista/core/partitioned.py#L111-L122
user27182: This suggested change could also be made now that `zip` is fixed, either this PR or separate if preferred, cc @tkoyama010 

https://github.com/pyvista/pyvista/pull/6599/files#r1739929981
pyvista-bot: <!-- NETLIFY DEPLOY COMMENT GENERATED BY ACTIONS_NETLIFY - APP ID SHA256: d3574f413fe51079937885070831e027ca5b10f7facb9e609494e0a481a31f79 -->
🚀 Deployed on https://66e2092129dd6c80adf5e12b--pyvista-dev.netlify.app
tkoyama010: > This suggested change could also be made now that `zip` is fixed, either this PR or separate if preferred

Fixed in 8b90fd5f500219e9aeac48a242d9fe63d547ef36 :+1: 
tkoyama010: pre-commit.ci autofix

Patch:
diff --git a/pyvista/core/composite.py b/pyvista/core/composite.py
index f11d1634..b6b63d3c 100644
--- a/pyvista/core/composite.py
+++ b/pyvista/core/composite.py
@@ -760,11 +760,6 @@ class MultiBlock(
         if hasattr(dataset, 'memory_address'):
             self._refs.pop(dataset.memory_address, None)  # type: ignore[union-attr]
 
-    def __iter__(self) -> MultiBlock:
-        """Return the iterator across all blocks."""
-        self._iter_n = 0
-        return self
-
     def __eq__(self, other):
         """Equality comparison."""
         if not isinstance(other, MultiBlock):
@@ -781,14 +776,6 @@ class MultiBlock(
 
         return not any(self_mesh != other_mesh for self_mesh, other_mesh in zip(self, other))
 
-    def __next__(self) -> _TypeMultiBlockLeaf | None:
-        """Get the next block from the iterator."""
-        if self._iter_n < self.n_blocks:
-            result = self[self._iter_n]
-            self._iter_n += 1
-            return result
-        raise StopIteration
-
     def insert(self, index: int, dataset: _TypeMultiBlockLeaf, name: str | None = None) -> None:
         """Insert data before index.
 


Test patch:
diff --git a/tests/core/test_composite.py b/tests/core/test_composite.py
index 217eed94..29b5cc01 100644
--- a/tests/core/test_composite.py
+++ b/tests/core/test_composite.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import itertools
 import pathlib
 import platform
 import weakref
@@ -440,9 +441,8 @@ def test_transform_filter(ant, sphere, airplane, tetbeam, inplace):
     keys_after = output.keys()
 
     assert (output is multi) == inplace
-    # https://github.com/pyvista/pyvista/pull/6599/files#r1739931261
-    for i, _ in enumerate(multi):
-        assert (multi[i] is output[i]) == inplace or (multi[i] is None)
+    for block_in, block_out in zip(multi, output):
+        assert (block_in is block_out) == inplace or (block_in is None)
     assert np.allclose(bounds_before + NUMBER, bounds_after)
     assert n_blocks_before == n_blocks_after
     assert keys_before == keys_after
@@ -838,3 +838,16 @@ def test_clear_all_cell_data(multiblock_all):
         else:
             assert block.point_data.keys() != []
             assert block.cell_data.keys() == []
+
+
+def test_multi_block_zip():
+    # Test `__iter__` and `__next__` inheritance
+    list_ = [None, None]
+    multi = MultiBlock(list_)
+    zipped_multi = list(zip(multi, multi))
+    zipped_list = list(zip(list_, list_))
+
+    assert len(zipped_multi) == len(zipped_list)
+    assert len(zipped_multi[0]) == len(zipped_list[0])
+    for i, j in itertools.product(range(2), repeat=2):
+        assert zipped_multi[i][j] is zipped_list[i][j] is None