[Python-checkins] cpython: inspect.Signature: Fix discrepancy between __eq__ and __hash__.

yury.selivanov python-checkins at python.org
Fri Sep 12 21:48:12 CEST 2014


http://hg.python.org/cpython/rev/3b974b61e74d
changeset:   92410:3b974b61e74d
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Fri Sep 12 15:48:02 2014 -0400
summary:
  inspect.Signature: Fix discrepancy between __eq__ and __hash__.

Issue #20334. Thanks to Antony Lee for bug report & initial patch.

files:
  Lib/inspect.py           |  55 +++++++--------------------
  Lib/test/test_inspect.py |  24 ++++++++++++
  Misc/NEWS                |   1 +
  3 files changed, 40 insertions(+), 40 deletions(-)


diff --git a/Lib/inspect.py b/Lib/inspect.py
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -2239,14 +2239,7 @@
                                            id(self), self)
 
     def __hash__(self):
-        hash_tuple = (self.name, int(self.kind))
-
-        if self._annotation is not _empty:
-            hash_tuple += (self._annotation,)
-        if self._default is not _empty:
-            hash_tuple += (self._default,)
-
-        return hash(hash_tuple)
+        return hash((self.name, self.kind, self.annotation, self.default))
 
     def __eq__(self, other):
         return (issubclass(other.__class__, Parameter) and
@@ -2541,41 +2534,23 @@
         return type(self)(parameters,
                           return_annotation=return_annotation)
 
+    def _hash_basis(self):
+        params = tuple(param for param in self.parameters.values()
+                             if param.kind != _KEYWORD_ONLY)
+
+        kwo_params = {param.name: param for param in self.parameters.values()
+                                        if param.kind == _KEYWORD_ONLY}
+
+        return params, kwo_params, self.return_annotation
+
     def __hash__(self):
-        hash_tuple = tuple(self.parameters.values())
-        if self._return_annotation is not _empty:
-            hash_tuple += (self._return_annotation,)
-        return hash(hash_tuple)
+        params, kwo_params, return_annotation = self._hash_basis()
+        kwo_params = frozenset(kwo_params.values())
+        return hash((params, kwo_params, return_annotation))
 
     def __eq__(self, other):
-        if (not issubclass(type(other), Signature) or
-                    self.return_annotation != other.return_annotation or
-                    len(self.parameters) != len(other.parameters)):
-            return False
-
-        other_positions = {param: idx
-                           for idx, param in enumerate(other.parameters.keys())}
-
-        for idx, (param_name, param) in enumerate(self.parameters.items()):
-            if param.kind == _KEYWORD_ONLY:
-                try:
-                    other_param = other.parameters[param_name]
-                except KeyError:
-                    return False
-                else:
-                    if param != other_param:
-                        return False
-            else:
-                try:
-                    other_idx = other_positions[param_name]
-                except KeyError:
-                    return False
-                else:
-                    if (idx != other_idx or
-                                    param != other.parameters[param_name]):
-                        return False
-
-        return True
+        return (isinstance(other, Signature) and
+                self._hash_basis() == other._hash_basis())
 
     def __ne__(self, other):
         return not self.__eq__(other)
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -2535,43 +2535,67 @@
 
         def bar(a, *, b:int) -> float: pass
         self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def bar(a, *, b:int) -> int: pass
         self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def bar(a, *, b:int): pass
         self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def bar(a, *, b:int=42) -> float: pass
         self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def bar(a, *, c) -> float: pass
         self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def bar(a, b:int) -> float: pass
         self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
         def spam(b:int, a) -> float: pass
         self.assertNotEqual(inspect.signature(spam), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(spam)), hash(inspect.signature(bar)))
 
         def foo(*, a, b, c): pass
         def bar(*, c, b, a): pass
         self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def foo(*, a=1, b, c): pass
         def bar(*, c, b, a=1): pass
         self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def foo(pos, *, a=1, b, c): pass
         def bar(pos, *, c, b, a=1): pass
         self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def foo(pos, *, a, b, c): pass
         def bar(pos, *, c, b, a=1): pass
         self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertNotEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
         def foo(pos, *args, a=42, b, c, **kwargs:int): pass
         def bar(pos, *args, c, b, a=42, **kwargs:int): pass
         self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+        self.assertEqual(
+            hash(inspect.signature(foo)), hash(inspect.signature(bar)))
 
     def test_signature_hashable(self):
         S = inspect.Signature
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -814,6 +814,7 @@
   keyword-only.
 
 - Issue #20334: inspect.Signature and inspect.Parameter are now hashable.
+  Thanks to Antony Lee for bug reports and suggestions.
 
 - Issue #15916: doctest.DocTestSuite returns an empty unittest.TestSuite instead
   of raising ValueError if it finds no tests

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list