From b769cae99838cabaaabdba4267cf365c96f56ca2 Mon Sep 17 00:00:00 2001 From: Bobby Morck Date: Thu, 6 Jul 2023 16:15:26 -0400 Subject: [PATCH 1/2] Add Ignore List Order Option to DeepHash --- deepdiff/deephash.py | 6 +++++- tests/test_hash.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index c93037d8..9547730a 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -144,6 +144,7 @@ def __init__(self, parent="root", encodings=None, ignore_encoding_errors=False, + ignore_list_order=True, **kwargs): if kwargs: raise ValueError( @@ -190,6 +191,7 @@ def __init__(self, self.ignore_private_variables = ignore_private_variables self.encodings = encodings self.ignore_encoding_errors = ignore_encoding_errors + self.ignore_list_order = ignore_list_order self._hash(obj, parent=parent, parents_ids=frozenset({get_id(obj)})) @@ -424,7 +426,9 @@ def _prep_iterable(self, obj, parent, parents_ids=EMPTY_FROZENSET): '{}|{}'.format(i, v) for i, v in result.items() ] - result = sorted(map(str, result)) # making sure the result items are string and sorted so join command works. + result = map(str, result) # making sure the result items are string so join command works. + if self.ignore_list_order: + result = sorted(result) result = ','.join(result) result = KEY_TO_VAL_STR.format(type(obj).__name__, result) diff --git a/tests/test_hash.py b/tests/test_hash.py index da94130d..f56be5c3 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -368,6 +368,21 @@ def test_same_sets_same_hash(self): t2_hash = DeepHashPrep(t2) assert t1_hash[get_id(t1)] == t2_hash[get_id(t2)] + + @pytest.mark.parametrize("list1, list2, ignore_list_order, is_equal", [ + ([1, 2], [2, 1], False, False), + ([1, 2], [2, 1], True, True), + ([1, 2, 3], [1, 3, 2], False, False), + ([1, [1, 2, 3]], [1, [3, 2, 1]], False, False), + ([1, [1, 2, 3]], [1, [3, 2, 1]], True, True), + ((1, 2), (2, 1), False, False), + ((1, 2), (2, 1), True, True), + ]) + def test_list_ignore_order(self, list1, list2, ignore_list_order, is_equal): + list1_hash = DeepHash(list1, ignore_list_order=ignore_list_order) + list2_hash = DeepHash(list2, ignore_list_order=ignore_list_order) + + assert is_equal == (list1_hash[list1] == list2_hash[list2]) @pytest.mark.parametrize("t1, t2, significant_digits, number_format_notation, result", [ ({0.012, 0.98}, {0.013, 0.99}, 1, "f", 'set:float:0.0,float:1.0'), From b2fcd658608ee924d1cdf9affdb811e947ed4b8f Mon Sep 17 00:00:00 2001 From: Bobby Morck Date: Wed, 12 Jul 2023 14:21:42 -0400 Subject: [PATCH 2/2] Update docs and rename to ignore_iterable_order --- deepdiff/deephash.py | 6 +++--- docs/deephash_doc.rst | 2 ++ tests/test_hash.py | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index 9547730a..eb9b9f11 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -144,7 +144,7 @@ def __init__(self, parent="root", encodings=None, ignore_encoding_errors=False, - ignore_list_order=True, + ignore_iterable_order=True, **kwargs): if kwargs: raise ValueError( @@ -191,7 +191,7 @@ def __init__(self, self.ignore_private_variables = ignore_private_variables self.encodings = encodings self.ignore_encoding_errors = ignore_encoding_errors - self.ignore_list_order = ignore_list_order + self.ignore_iterable_order = ignore_iterable_order self._hash(obj, parent=parent, parents_ids=frozenset({get_id(obj)})) @@ -427,7 +427,7 @@ def _prep_iterable(self, obj, parent, parents_ids=EMPTY_FROZENSET): ] result = map(str, result) # making sure the result items are string so join command works. - if self.ignore_list_order: + if self.ignore_iterable_order: result = sorted(result) result = ','.join(result) result = KEY_TO_VAL_STR.format(type(obj).__name__, result) diff --git a/docs/deephash_doc.rst b/docs/deephash_doc.rst index 82e8c361..a5aa9f1f 100644 --- a/docs/deephash_doc.rst +++ b/docs/deephash_doc.rst @@ -123,6 +123,8 @@ ignore_private_variables: Boolean, default = True ignore_encoding_errors: Boolean, default = False If you want to get away with UnicodeDecodeError without passing explicit character encodings, set this option to True. If you want to make sure the encoding is done properly, keep this as False and instead pass an explicit list of character encodings to be considered via the encodings parameter. +ignore_iterable_order: Boolean, default = True + If order of items in an iterable should not cause the hash of the iterable to be different. number_format_notation : string, default="f" number_format_notation is what defines the meaning of significant digits. The default value of "f" means the digits AFTER the decimal point. "f" stands for fixed point. The other option is "e" which stands for exponent notation or scientific notation. diff --git a/tests/test_hash.py b/tests/test_hash.py index f56be5c3..bbf2c0ef 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -369,7 +369,7 @@ def test_same_sets_same_hash(self): assert t1_hash[get_id(t1)] == t2_hash[get_id(t2)] - @pytest.mark.parametrize("list1, list2, ignore_list_order, is_equal", [ + @pytest.mark.parametrize("list1, list2, ignore_iterable_order, is_equal", [ ([1, 2], [2, 1], False, False), ([1, 2], [2, 1], True, True), ([1, 2, 3], [1, 3, 2], False, False), @@ -378,9 +378,9 @@ def test_same_sets_same_hash(self): ((1, 2), (2, 1), False, False), ((1, 2), (2, 1), True, True), ]) - def test_list_ignore_order(self, list1, list2, ignore_list_order, is_equal): - list1_hash = DeepHash(list1, ignore_list_order=ignore_list_order) - list2_hash = DeepHash(list2, ignore_list_order=ignore_list_order) + def test_ignore_iterable_order(self, list1, list2, ignore_iterable_order, is_equal): + list1_hash = DeepHash(list1, ignore_iterable_order=ignore_iterable_order) + list2_hash = DeepHash(list2, ignore_iterable_order=ignore_iterable_order) assert is_equal == (list1_hash[list1] == list2_hash[list2])