diff --git a/lib/iris/_concatenate.py b/lib/iris/_concatenate.py index 714c758861..c88a5257a3 100644 --- a/lib/iris/_concatenate.py +++ b/lib/iris/_concatenate.py @@ -279,7 +279,11 @@ def concatenate(cubes, error_on_mismatch=False): # Construct a concatenated cube from each of the proto-cubes. concatenated_cubes = iris.cube.CubeList() - for name in sorted(proto_cubes_by_name): + # Emulate Python 2 behaviour. + def _none_sort(item): + return (item is not None, item) + + for name in sorted(proto_cubes_by_name, key=_none_sort): for proto_cube in proto_cubes_by_name[name]: # Construct the concatenated cube. concatenated_cubes.append(proto_cube.concatenate()) diff --git a/lib/iris/_merge.py b/lib/iris/_merge.py index a4e21c623f..ebb1d71131 100644 --- a/lib/iris/_merge.py +++ b/lib/iris/_merge.py @@ -24,6 +24,7 @@ from __future__ import (absolute_import, division, print_function) from six.moves import (filter, input, map, range, zip) # noqa +import six from collections import namedtuple, OrderedDict from copy import deepcopy @@ -1300,7 +1301,8 @@ def _define_space(self, space, positions, indexes, function_matrix): def axis_and_name(name): axis_dict = {'T': 1, 'Z': 2, 'Y': 3, 'X': 4} axis_index = axis_dict.get(self._guess_axis(name), 0) - return (axis_index, name) + # The middle element ensures sorting is the same as Python 2. + return (axis_index, not isinstance(name, six.integer_types), name) names = sorted(space, key=axis_and_name) dim_by_name = {} diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 0ae8071c42..cba498c29f 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -64,6 +64,20 @@ def name(self, default='unknown'): """ return self.standard_name or self.long_name or self.var_name or default + def __lt__(self, other): + if not isinstance(other, CoordDefn): + return NotImplemented + + def _sort_key(defn): + # Emulate Python 2 behaviour with None + return (defn.standard_name is not None, defn.standard_name, + defn.long_name is not None, defn.long_name, + defn.var_name is not None, defn.var_name, + defn.units is not None, defn.units, + defn.coord_system is not None, defn.coord_system) + + return _sort_key(self) < _sort_key(other) + class CoordExtent(collections.namedtuple('_CoordExtent', ['name_or_coord', 'minimum', diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 5bdf060863..97ef8df2a2 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -452,9 +452,13 @@ def merge(self, unique=True): proto_cube = iris._merge.ProtoCube(cube) proto_cubes.append(proto_cube) + # Emulate Python 2 behaviour. + def _none_sort(item): + return (item is not None, item) + # Extract all the merged cubes from the ProtoCubes. merged_cubes = CubeList() - for name in sorted(proto_cubes_by_name): + for name in sorted(proto_cubes_by_name, key=_none_sort): for proto_cube in proto_cubes_by_name[name]: merged_cubes.extend(proto_cube.merge(unique=unique)) diff --git a/lib/iris/io/format_picker.py b/lib/iris/io/format_picker.py index bf7d5b7f34..d5c731356b 100644 --- a/lib/iris/io/format_picker.py +++ b/lib/iris/io/format_picker.py @@ -211,17 +211,20 @@ def handler(self): """The handler function of this FileFormat. (Read only)""" return self._handler + def _sort_key(self): + return (-self.priority, self.name, self.file_element) + def __lt__(self, other): if not isinstance(other, FormatSpecification): return NotImplemented - return (-self.priority, hash(self)) < (-other.priority, hash(other)) + return self._sort_key() < other._sort_key() def __eq__(self, other): if not isinstance(other, FormatSpecification): return NotImplemented - return self.priority == other.priority and hash(self) == hash(other) + return self._sort_key() == other._sort_key() def __ne__(self, other): return not (self == other) diff --git a/lib/iris/tests/results/file_load/known_loaders.txt b/lib/iris/tests/results/file_load/known_loaders.txt index 28edcdf8b7..9b0a074574 100644 --- a/lib/iris/tests/results/file_load/known_loaders.txt +++ b/lib/iris/tests/results/file_load/known_loaders.txt @@ -1,16 +1,16 @@ * NetCDF OPeNDAP (priority 6) * NAME III (priority 5) - * NetCDF_v4 (priority 5) - * UM Post Processing file (PP) (priority 5) * NetCDF (priority 5) * NetCDF 64 bit offset format (priority 5) + * NetCDF_v4 (priority 5) + * UM Post Processing file (PP) (priority 5) * UM Fieldsfile (FF) post v5.2 (priority 4) - * UM Fieldsfile (FF) pre v3.1 (priority 3) - * UM Fieldsfile (FF) ancillary (priority 3) - * UM Post Processing file (PP) little-endian (priority 3) - * UM Fieldsfile (FF) converted with ieee to 32 bit (priority 3) - * UM Fieldsfile (FF) ancillary converted with ieee to 32 bit (priority 3) - * NIMROD (priority 3) * ABF (priority 3) * ABL (priority 3) + * NIMROD (priority 3) + * UM Fieldsfile (FF) ancillary (priority 3) + * UM Fieldsfile (FF) ancillary converted with ieee to 32 bit (priority 3) + * UM Fieldsfile (FF) converted with ieee to 32 bit (priority 3) + * UM Fieldsfile (FF) pre v3.1 (priority 3) + * UM Post Processing file (PP) little-endian (priority 3) * GRIB (priority 1) \ No newline at end of file diff --git a/lib/iris/tests/unit/fileformats/grib/load_rules/test_convert.py b/lib/iris/tests/unit/fileformats/grib/load_rules/test_convert.py index ea879cfa2b..120a72a880 100644 --- a/lib/iris/tests/unit/fileformats/grib/load_rules/test_convert.py +++ b/lib/iris/tests/unit/fileformats/grib/load_rules/test_convert.py @@ -96,7 +96,8 @@ def assert_bounded_message(self, **kwargs): 'edition': 1, '_forecastTime': 15, '_forecastTimeUnit': 'hours', 'phenomenon_bounds': lambda u: (80, 120), - '_phenomenonDateTime': -1} + '_phenomenonDateTime': -1, + 'table2Version': 9999} attributes.update(kwargs) message = mock.Mock(**attributes) self._test_for_coord(message, convert, self.is_forecast_period, diff --git a/lib/iris/tests/unit/fileformats/pp_rules/test__all_other_rules.py b/lib/iris/tests/unit/fileformats/pp_rules/test__all_other_rules.py index ee1847ee16..7b716e6095 100644 --- a/lib/iris/tests/unit/fileformats/pp_rules/test__all_other_rules.py +++ b/lib/iris/tests/unit/fileformats/pp_rules/test__all_other_rules.py @@ -118,9 +118,9 @@ def test_other_lbtim_ib(self): self.assertEqual(res, expected) def test_multiple_unordered_lbprocs(self): - field = mock.MagicMock(lbproc=192, + field = mock.MagicMock(lbproc=192, bzx=0, bdx=1, lbnpt=3, lbrow=3, lbtim=mock.Mock(ia=24, ib=5, ic=3), - lbcode=SplittableInt(1), + lbcode=SplittableInt(1), x_bounds=None, _x_coord_name=lambda: 'longitude', _y_coord_name=lambda: 'latitude') res = _all_other_rules(field)[CELL_METHODS_INDEX] @@ -129,9 +129,9 @@ def test_multiple_unordered_lbprocs(self): self.assertEqual(res, expected) def test_multiple_unordered_rotated_lbprocs(self): - field = mock.MagicMock(lbproc=192, + field = mock.MagicMock(lbproc=192, bzx=0, bdx=1, lbnpt=3, lbrow=3, lbtim=mock.Mock(ia=24, ib=5, ic=3), - lbcode=SplittableInt(101), + lbcode=SplittableInt(101), x_bounds=None, _x_coord_name=lambda: 'grid_longitude', _y_coord_name=lambda: 'grid_latitude') res = _all_other_rules(field)[CELL_METHODS_INDEX]