diff --git a/docs/iris/src/userguide/subsetting_a_cube.rst b/docs/iris/src/userguide/subsetting_a_cube.rst index b61f16a043..5864de531a 100644 --- a/docs/iris/src/userguide/subsetting_a_cube.rst +++ b/docs/iris/src/userguide/subsetting_a_cube.rst @@ -103,6 +103,9 @@ same way as loading with constraints: Cube iteration ^^^^^^^^^^^^^^^ +It is not possible to directly iterate over an Iris cube. That is, you cannot use code such as +``for x in cube:``. However, you can iterate over cube slices, as this section details. + A useful way of dealing with a Cube in its **entirety** is by iterating over its layers or slices. For example, to deal with a 3 dimensional cube (z,y,x) you could iterate over all 2 dimensional slices in y and x which make up the full 3d cube.:: diff --git a/docs/iris/src/whatsnew/contributions_3.0.0/bugfix_2020-Feb-13_cube_iter_remove.txt b/docs/iris/src/whatsnew/contributions_3.0.0/bugfix_2020-Feb-13_cube_iter_remove.txt new file mode 100644 index 0000000000..082cd8acc8 --- /dev/null +++ b/docs/iris/src/whatsnew/contributions_3.0.0/bugfix_2020-Feb-13_cube_iter_remove.txt @@ -0,0 +1,3 @@ +* The `__iter__()` method in class:`iris.cube.Cube` was set to `None`. + `TypeError` is still raised if a `Cube` is iterated over but + `isinstance(cube, collections.Iterable)` now behaves as expected. \ No newline at end of file diff --git a/lib/iris/cube.py b/lib/iris/cube.py index a246b97e1c..0fc381725d 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -2622,8 +2622,9 @@ def _repr_html_(self): representer = CubeRepresentation(self) return representer.repr_html() - def __iter__(self): - raise TypeError("Cube is not iterable") + # Indicate that the iter option is not available. Python will raise + # TypeError with a useful message if a Cube is iterated over. + __iter__ = None def __getitem__(self, keys): """ diff --git a/lib/iris/tests/test_cdm.py b/lib/iris/tests/test_cdm.py index 2c006e0e4f..ab27ad6040 100644 --- a/lib/iris/tests/test_cdm.py +++ b/lib/iris/tests/test_cdm.py @@ -11,6 +11,7 @@ # import iris tests first so that some things can be initialised before importing anything else import iris.tests as tests +import collections import os import re @@ -690,6 +691,9 @@ def test_cube_iteration(self): for subcube in self.t: pass + def test_not_iterable(self): + self.assertFalse(isinstance(self.t, collections.Iterable)) + class Test2dSlicing(TestCube2d): def test_cube_slice_all_dimensions(self): diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py index 2e0fb2d12d..6870e2367f 100644 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ b/lib/iris/tests/unit/cube/test_CubeList.py @@ -7,6 +7,8 @@ # Import iris.tests first so that some things can be initialised before # importing anything else. +import collections + import iris.tests as tests import iris.tests.stock @@ -292,6 +294,22 @@ def test_scalar_cube_data_constraint(self): self.assertEqual(res, expected) +class Test_iteration(tests.IrisTest): + def setUp(self): + self.scalar_cubes = CubeList() + for i in range(5): + for letter in "abcd": + self.scalar_cubes.append(Cube(i, long_name=letter)) + + def test_iterable(self): + self.assertTrue(isinstance(self.scalar_cubes, collections.Iterable)) + + def test_iteration(self): + letters = "abcd" * 5 + for i, cube in enumerate(self.scalar_cubes): + self.assertEqual(cube.long_name, letters[i]) + + class TestPrint(tests.IrisTest): def setUp(self): self.cubes = CubeList([iris.tests.stock.lat_lon_cube()])